Smooth random line, with loops, using Raphael/JS - javascript

$(document).ready(function(){
var x1 = Math.random()*$(window).width(); var y1 = Math.random()*$(window).height();
var x2 = 1; var y2 = 1;
var paper = Raphael(document.body);
setInterval(function() {
randx = Math.random(); randy = Math.random();
if (randx > 0.9) {
if (x2 = 1) {
if (randx > 0.99) x2 = -1;
}
else if (x2 = -1) {
if (randx > 0.99) x2 = 1;
}
} else x2 = 0;
if (randy > 0.9) {
if (y2 = 1) {
if (randy > 0.99) y2 = -1;
}
else if (y2 = -1) {
if (randy > 0.99) y2 = 1;
}
} else y2 = 0;
paper.path("M"+x1+" "+y1+"L"+(x1+x2)+" "+(y1+y2));
x1 = x1+x2;
y1 = y1+y2;
}, 0);
});
This is my "random line" generating script. I know it must look terrible, I am just learning. But I am trying to get something resembling this: http://i.stack.imgur.com/R7Kkv.png
I'd really appreciate some tips/suggestions for the algorithm that will make the line smoother and more likely to turn do a u-turn etc.
Thanks

The answer is to generate points within a 40x40 or so box around the previous point, and interpolate between them with a cubic spline.

Related

Matrix variable is not undefined in javascript

I'm coding a maze generator using the backtracking algorithm and p5.js library, but when I was coding a loop to set random positions in the maze, I noticed an error in javascript: "maze[(x - 2)] is undefined", but this variable is declared! I tried to read about data structures, but I couldn't find a solution for this problem.
What is happening?
Can someone help me?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset = "utf-8">
<title>Maze</title>
</head>
<body>
<script src = "https://cdn.jsdelivr.net/npm/p5#1.4.0/lib/p5.js"></script>
<script>
var width = 720;
var height = 400;
var x, y;
var maze = Array(39).fill(Array(71).fill(0));
function setup()
{
createCanvas(720, 400);
}
function draw()
{
//inicial position
x = 1;
y = 1;
maze[x][y] = 1;
//grid
strokeWeight(1);
stroke('BLACK');
for (x = 0; x <= 720; x += 10) line(x, 0, x, 400);
for (y = 0; y <= 400; y += 10) line(0, y, 720, y);
found = 0;
do
{
direction = Math.floor(Math.random() * 4);
if (direction == 0 && (y != width - 2 && maze[x][y + 2] == 0))
{
x2 = x;
y2 = y;
y += 2;
maze[x][y] = 1;
maze[x][y-1] = 1;
found = 1;
}
else if (direction == 1 && (x > 1 && maze[x - 2][y] == 0))
{
x2 = x;
y2 = y;
x -= 2;
maze[x][y] = 1;
maze[x + 1][y] = 1;
found = 1;
}
else if (direction == 2 && (y > 1 && maze[x][y - 2] == 0))
{
x2 = x;
y2 = y;
y -= 2;
maze[x][y] = 1;
maze[x][y + 1] = 1;
found = 1;
}
else if (direction == 3 && (x != height - 2 && maze[x + 2][y] == 0))
{
x2 = x;
y2 = y;
x += 2;
maze[x][y] = 1;
maze[x - 1][y] = 1;
found = 1;
}
} while (found == 0);
noLoop();
}
</script>
</body>
</html>
There a few issues with this code.
Lets start with why you getting that error. The issue is that you are reusing global x and y variables in for loop that draw lines, when the loop is done, it set these variables larger than your maze array.
This brings us to second issue: maze array is smaller than width/height of the canvas.
The code is not 100% working so I don't exactly understand what it supposed to do, but it seems x and y should not exceed width/height, however you have a condition y != width - 2 which will be true even if y is bigger than width (btw, shouldn't it be height instead??)
And finally, through out the code you have multiple places with 720 and 400, instead of using width and height variables. This is a bad practice.
const width = 720;
const height = 400;
var x, y;
var maze = Array(width+1).fill(Array(height+1).fill(0));
function setup()
{
createCanvas(width, height);
}
function draw()
{
//inicial position
x = 1;
y = 1;
maze[x][y] = 1;
//grid
strokeWeight(1);
stroke('BLACK');
for (let x = 0; x <= width; x += 10) line(x, 0, x, height);
for (let y = 0; y <= height; y += 10) line(0, y, width, y);
found = 0;
do
{
direction = Math.floor(Math.random() * 4);
if (direction == 0 && (y <= height - 2 && maze[x][y + 2] == 0))
{
x2 = x;
y2 = y;
y += 2;
maze[x][y] = 1;
maze[x][y-1] = 1;
found = 1;
}
else if (direction == 1 && (x > 1 && maze[x - 2][y] == 0))
{
x2 = x;
y2 = y;
x -= 2;
maze[x][y] = 1;
maze[x + 1][y] = 1;
found = 1;
}
else if (direction == 2 && (y > 1 && maze[x][y - 2] == 0))
{
x2 = x;
y2 = y;
y -= 2;
maze[x][y] = 1;
maze[x][y + 1] = 1;
found = 1;
}
else if (direction == 3 && (x <= width - 2 && maze[x + 2][y] == 0))
{
x2 = x;
y2 = y;
x += 2;
maze[x][y] = 1;
maze[x - 1][y] = 1;
found = 1;
}
} while (found == 0);
noLoop();
}
<script src = "https://cdn.jsdelivr.net/npm/p5#1.4.0/lib/p5.js"></script>

Math - get the smallest polygon that the lines close around chart starting point

I'm trying to get the points of the smallest polygons that the lines create around the starting point of the chart, that is, the innermost polygon. The lines vary depending on some parameters that can be changed.
Example of such a chart:
As is visible from the chart, the lines intersect many times and, thus, create multiple polygons. However, I am interested in getting only the smallest polygon that is within the starting point (the center) of the chart.
I was thinking about drawing multiple parallels with x-axis from the y-axis to left and right from the top and the bottom (the smallest intercept on +y and -y axis) and seeing which line gets "hit" first as to get the lines which would enclose this polygon, then getting their intersections for vertices which can be used to drawing the polygon. However, since it will take numerous points to check such lines with precision, I am wondering if there is perhaps a more elegant solution to the problem?
I managed to solve this with the help of Stef and his algorithm. Here is the code I used:
const getIntersections = async (
lines: IPoint[][]
): Promise<IIntersectLine[]> => {
let lineIntersects: IIntersectLine[] = [];
lines.forEach((line) => {
let lineIntersect: IIntersectLine = {
line: line,
intersects: [],
};
let x1 = line[1].x;
let y1 = line[1].y;
let x2 = line[2].x;
let y2 = line[2].y;
for (let i = 0; i < lines.length; i++) {
let x3 = lines[i][1].x;
let y3 = lines[i][1].y;
let x4 = lines[i][2].x;
let y4 = lines[i][2].y;
if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) continue;
let denominator = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (denominator === 0) continue;
let ua =
((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
let ub =
((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
if (ua < 0 || ua > 1 || ub < 0 || ub > 1) continue;
let x = x1 + ua * (x2 - x1);
let y = y1 + ua * (y2 - y1);
lineIntersect.intersects.push({
x: +x.toFixed(4),
y: +y.toFixed(4),
});
}
lineIntersect.intersects.sort((a, b) => {
return a.x - b.x;
});
lineIntersects.push(lineIntersect);
});
return lineIntersects;
};
const getStartingPoint = async (intersects: IPoint[]) => {
let result: IPoint = intersects[0];
let distance = result.x * result.x + result.y * result.y;
intersects.forEach((i) => {
let newDistance = i.x * i.x + i.y * i.y;
if (newDistance < distance) {
distance = newDistance;
result = i;
}
});
return result;
};
const calcPolygonArea = async (polygon: IPoint[]) => {
let total = 0;
for (let i = 0, l = polygon.length; i < l; i++) {
let addX = polygon[i].x;
let addY = polygon[i == polygon.length - 1 ? 0 : i + 1].y;
let subX = polygon[i == polygon.length - 1 ? 0 : i + 1].x;
let subY = polygon[i].y;
total += addX * addY * 0.5;
total -= subX * subY * 0.5;
}
return Math.abs(total);
};
export const getPolygonVertices = async (lines: IPoint[][]) => {
let result: IPoint[] = [];
let intersections = await getIntersections(lines);
let intersectionVertices = intersections.map((x) => x.intersects).flat();
let startingPoint = await getStartingPoint(intersectionVertices);
let crossedLines = intersections.filter((x) =>
x.intersects.some(
(p) => p.x === startingPoint.x && p.y === startingPoint.y
)
);
let newPoints: IPoint[] = [];
const x0 = 0;
const y0 = 0;
crossedLines.forEach((line) => {
let x1 = startingPoint.x;
let y1 = startingPoint.y;
let pointIndex = line.intersects.findIndex(
(p) => p.x === startingPoint.x && p.y === startingPoint.y
);
let d;
if (line.intersects[pointIndex - 1]) {
let x2 = line.intersects[pointIndex - 1].x;
let y2 = line.intersects[pointIndex - 1].y;
d = (x0 - x1) * (y2 - y1) - (y0 - y1) * (x2 - x1);
if (d > 0) newPoints.push({ x: x2, y: y2 });
}
if (line.intersects[pointIndex + 1]) {
let x2 = line.intersects[pointIndex + 1].x;
let y2 = line.intersects[pointIndex + 1].y;
d = (x0 - x1) * (y2 - y1) - (y0 - y1) * (x2 - x1);
if (d > 0) newPoints.push({ x: x2, y: y2 });
}
});
let result1: IPoint[] = [];
let result2: IPoint[] = [];
for (let i = 0; i < newPoints.length; i++) {
let tempResult: IPoint[] = [];
tempResult.push(startingPoint, newPoints[i]);
for (let j = 0; j < 50; j++) {
const uniqueValues = new Set(tempResult.map((v) => v.x));
if (uniqueValues.size < tempResult.length) {
if (i === 0) result1 = tempResult;
else result2 = tempResult;
break;
}
let newCrossedLines = intersections.filter((x) =>
x.intersects.some(
(p) =>
p.x === tempResult[tempResult.length - 1].x &&
p.y === tempResult[tempResult.length - 1].y
)
);
let newLine = newCrossedLines.filter((l) =>
l.intersects.every(
(p) =>
p.x !== tempResult[tempResult.length - 2].x &&
p.y !== tempResult[tempResult.length - 2].y
)
)[0];
let x1 = tempResult[tempResult.length - 1].x;
let y1 = tempResult[tempResult.length - 1].y;
let pointIndex = newLine.intersects.findIndex(
(p) =>
p.x === tempResult[tempResult.length - 1].x &&
p.y === tempResult[tempResult.length - 1].y
);
let d;
if (newLine.intersects[pointIndex - 1]) {
let x2 = newLine.intersects[pointIndex - 1].x;
let y2 = newLine.intersects[pointIndex - 1].y;
d = (x0 - x1) * (y2 - y1) - (y0 - y1) * (x2 - x1);
if (d > 0) tempResult.push({ x: x2, y: y2 });
}
if (newLine.intersects[pointIndex + 1]) {
let x2 = newLine.intersects[pointIndex + 1].x;
let y2 = newLine.intersects[pointIndex + 1].y;
d = (x0 - x1) * (y2 - y1) - (y0 - y1) * (x2 - x1);
if (d > 0) tempResult.push({ x: x2, y: y2 });
}
}
}
const area1 = await calcPolygonArea(result1);
const area2 = await calcPolygonArea(result2);
area1 < area2 ? (result = result1) : (result = result2);
return result;
};
Essentially, first I get all the intersections of all the lines on the chart. Then I find the closest one to the chart's starting point (0,0) as the smallest polygon enclosing it should contain that vertex. After that, I begin moving along the two lines that make up the closest intersection. Repeating the process for those two starting lines, I move clockwise along the line up to the next intersection, where I then move along the next line, continuing the process until I get a duplicate vertex in my result array, that is, until the polygon is closed. In the end, I compare the two polygons and return the smaller one.
There is most likely a more efficient way to do this, but this works for now!
End result:
Here is a possible algorithm:
While a cycle was not found:
Start at some point (x,y) on some line L
Find next intersection point (x',y') on L in clockwise direction
If the origin (0, 0) is on the right of this line:
x = x'
y = y'
L = that new line
If a cycle was found: this cycle is the polygon.

Canvas Animation: Pixels per Second with requestAnimationFrame

I have a function to move images by the canvas.
The run subfunction runs on each frame and when it returns true ends.
The problem is that the speed of movement depends on how many fps you have.
I would like to be able to put a variable that is pixels per second, so the image will always move at the same speed regardless of how many fps you have.
this.MoveImage = function(opts){
var image = opts.image;
var x1 = opts.x1;
var y1 = opts.y1;
var x2 = opts.x2;
var y2 = opts.y2;
var angle = Math.atan2(y2-y1, x2-x1);
var speed = opts.speed;
var xDirection = null;
var yDirection = null;
if(x1 < x2) xDirection = "positive";
else if(x1 > x2) xDirection = "negative";
if(y1 < y2) yDirection = "positive";
else if(y1 > y2) yDirection = "negative";
this.run = function(){
x1 += speed*Math.cos(angle);
y1 += speed*Math.sin(angle);
context.drawImage(image, x1, y1);
if(compare(x1, x2, xDirection) && compare(y1, y2, yDirection)){
return true;
}
}
function compare(n1, n2, direction){
if(n1 >= n2 && direction == "positive") return true;
else if(n1 <= n2 && direction == "negative") return true;
else if(n1 == n2 && direction == null) return true;
}
}

Is this a bug? (JavaScript conditional)

I've got some code for part of a game where an absolutely positioned div (id = 'cursor') follows your mouse's cursor around. When the cursor div runs into another div (class = 'thing') (these are the white boxes in the jsfiddles), the cursor speed changes.
In this JSfiddle you'll see that it works perfectly fine. When cursor hits thing, cursor speeds up. This is the conditional used to change the speed (newSpeed is what determines the speed for cursor):
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2){
newSpeed = 200;
changeCursorSpeed();
} else {
newSpeed = 12;
changeCursorSpeed();
console.log('hit');
}
The problem I'm having is when I switch around the values for newSpeed in the conditional:
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2){
newSpeed = 12;
changeCursorSpeed();
} else {
newSpeed = 200;
changeCursorSpeed();
console.log('hit');
}
Here's the JSFiddle for this. This causes the collision to not change newSpeed in the else part of the condition. However, using console.log('hit'), you can clearly see that the collision is detected.
Logically, this doesn't seem to make sense. I'd like to hear input from others about this as well as possible solutions. Thanks for any help.
Here's the entire code for the collision detection. The JSfiddles have a more complete code for the program.
var newSpeed;
var newInt = setInterval(function() {
function collision($cursor, $thing) {
var x1 = $cursor.offset().left;
var y1 = $cursor.offset().top;
var h1 = $cursor.outerHeight(true);
var w1 = $cursor.outerWidth(true);
var b1 = y1 + h1;
var r1 = x1 + w1;
$thing.each(function(i){
var x2 = $(this).offset().left;
var y2 = $(this).offset().top;
var h2 = $(this).outerHeight(true);
var w2 = $(this).outerWidth(true);
var b2 = y2 + h2;
var r2 = x2 + w2;
if (b1 < y2 || y1 > b2 || r1 < x2 || x1 > r2){
newSpeed = 12;
changeCursorSpeed();
} else {
newSpeed = 200;
changeCursorSpeed();
console.log('hit');
}
});
function changeCursorSpeed(){
$xp += (($mouseX - $xp)/newSpeed);
$yp += (($mouseY - $yp)/newSpeed);
$("#cursor").css({left:$xp +'px', top:$yp +'px'});
}
}
$(collision($('#cursor'), $('.thing')));
}, 20);

How to draw rectangle spiral?

I'm trying to draw a one pixel width line going form the canvas center and evolving with the canvas width/height ratio as it's drawn.
var x = 0;
var y = 0;
var dx = 0;
var dy = -1;
var width = 200;
var height = 40;
//var i = width * height;
var counter = 0;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
setInterval(function(){
//for (i = Math.pow(Math.max(width, height), 2); i>0; i--) {
if ((-width/2 < x <= width/2) && (-height/2 < y <= height/2)) {
console.log("[ " + x + " , " + y + " ]");
ctx.fillStyle = "#FF0000";
ctx.fillRect(width/2 + x, height/2 - y,1,1);
}
if (x === y || (x < 0 && x === -y) || (x > 0 && x === 1-y) || ( -width/2 > x > width/2 ) || ( -height/2 > y > height/2 ) ) {
// change direction
var tempdx = dx;
dx = -dy;
dy = tempdx;
}
counter += 1;
//alert (counter);
x += dx;
y += dy;
}, 1);
I want the spiral to evolve as such:
I'd like to be able to get the ratio between height and width on the equation, so I don't need to calculate the coordinates for points outside the canvas. Also, the purpose is for it to adjust the spiral drawing to the canvas proportions.
Any help would be appreciated.
A friend helped me handling a proper solution. I only have a 1 pixel offset to solve where I need to move all the drawing to the left by one pixel.
Here's the fiddle for the solution achieved: http://jsfiddle.net/hitbyatruck/c4Kd6/
And the Javascript code below:
var width = 150;
var height = 50;
var x = -(width - height)/2;
var y = 0;
var dx = 1;
var dy = 0;
var x_limit = (width - height)/2;
var y_limit = 0;
var counter = 0;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
setInterval(function(){
if ((-width/2 < x && x <= width/2) && (-height/2 < y && y <= height/2)) {
console.log("[ " + x + " , " + y + " ]");
ctx.fillStyle = "#FF0000";
ctx.fillRect(width/2 + x, height/2 - y,1,1);
}
if( dx > 0 ){//Dir right
if(x > x_limit){
dx = 0;
dy = 1;
}
}
else if( dy > 0 ){ //Dir up
if(y > y_limit){
dx = -1;
dy = 0;
}
}
else if(dx < 0){ //Dir left
if(x < (-1 * x_limit)){
dx = 0;
dy = -1;
}
}
else if(dy < 0) { //Dir down
if(y < (-1 * y_limit)){
dx = 1;
dy = 0;
x_limit += 1;
y_limit += 1;
}
}
counter += 1;
//alert (counter);
x += dx;
y += dy;
}, 1);
I nearly crashed my browser trying this. Here, have some code before I hurt myself!
It computes y=f(x) for the diagonal, and y2=f(x) for the antidiagonal, then checks if we're above or below the diagonals when needed.
var x = 0;
var y = 0;
var dx = 0;
var dy = -1;
var width = 200;
var height = 40;
//var i = width * height;
var counter = 0;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext('2d');
function diag1(x) {
return x*height/width;
}
function diag2(x) {
return -1/diag(x);
}
setInterval(function(){
//for (i = Math.pow(Math.max(width, height), 2); i>0; i--) {
if ((-width/2 < x && x <= width/2) && (-height/2 < y && y <= height/2)) {
console.log("[ " + x + " , " + y + " ]");
ctx.fillStyle = "#FF0000";
ctx.fillRect(width/2 + x, height/2 - y,1,1);
}
if (dx == 0) {
if (dy == 1) {
// moving up
if (y >= diag1(x)) {
// then move left
dy = 0;
dx = -1;
}
}
else {
// moving down
if (y <= diag2(x)) {
// then move right
dy = 0;
dx = 1;
}
}
}
else {
if (dx == 1) {
// moving right
if (y <= diag1(x)) {
// then move up
dy = 1;
dx = 0;
}
}
else {
// moving left
if (y <= diag2(x)) {
// then move down
dy = -1;
dx = 0;
}
}
}
counter += 1;
//alert (counter);
x += dx;
y += dy;
}, 1);

Categories