I'm writing a simple raycast in htm5l and the main issue with the raycast is that the line goes off in a direction of probably infinite length.. I would like to limit that length to a specific radius but I'm not having any luck. If someone could guide me that'd be great.
window.addEventListener('DOMContentLoaded', (event) => {
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let coord = {
x: 0,
y: 0
}
function line(x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Boundery {
constructor(x1, y1, x2, y2) {
this.a = new Vector(x1, y1);
this.b = new Vector(x2, y2);
}
show() {
ctx.strokeStyle = '#000000'
line(this.a.x, this.a.y, this.b.x, this.b.y);
}
}
class Ray {
constructor(x, y) {
this.pos = new Vector(x, y);
this.dir = new Vector(Math.cos(1), Math.sin(0));
}
show() {
ctx.strokeStyle = '#000000';
ctx.beginPath();
ctx.ellipse(this.pos.x, this.pos.y, 5, 5, 0, 0, Math.PI * 2);
ctx.stroke();
}
cast(wall) {
let x1 = wall.a.x;
let y1 = wall.a.y;
let x2 = wall.b.x;
let y2 = wall.b.y;
let x3 = this.pos.x;
let y3 = this.pos.y;
let x4 = this.pos.x + this.dir.x;
let y4 = this.pos.y + this.dir.y;
let den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (den == 0) {
return;
}
let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;
if (t > 0 && t < 1 && u > 0) {
let point = new Vector(x1 + t * (x2 - x1), y1 + t * (y2 - y1));
return point;
} else {
return;
}
}
}
let wall = new Boundery(300, 100, 300, 300);
let ray = new Ray(100, 200);
function tick(timestamp) {
ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
wall.show();
ray.show();
let r = ray.cast(wall);
if (r) {
ctx.fillStyle = 'red';
ctx.ellipse(r.x, r.y, 10, 10, 0, 0, 2 * Math.PI);
ctx.fill();
}
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
});
<canvas id="canvas" width="2000" height="1000"></canvas>
So the ray currently fires to the right (1,0) of the small red circle but it's distance just goes on forever so I'm trying to limit that distance. In the example the position the ray hits the wall is the red circle that's drawn on the wall
Ray length and direction
Modify the ray to have the start position, a direction, and a length. as follows
class Ray {
constructor(pos, direction, length) {
this.pos = pos;
this.dir = direction;
this.length = length;
}
You can get the end point with
get end() {
return new Vector(
this.pos.x + Math.cos(this.dir) * this.length,
this.pos.y + Math.sin(this.dir) * this.length
);
}
When you cast the ray you convert the ray to a line segment and then check against any wall segments for the intercept. Only points withing the length of the ray will be found.
Example.
The example uses a ray to check against many walls. It finds the closest intercept to the to the start of the ray and within the rays length.
Note (FOR example only) the walls are random so if a wall gets to close to the ray, click the canvas to randomize the walls.
I have re-organised it somewhat with Vector as a point, Line (2 vectors) as a line segment with an intercept function (also represents a wall), And Ray as a vector, direction and length. The Ray.cast finds the intercept of an array of line, returning undefined if no intercept found.
const ctx = canvas.getContext("2d");
Math.TAU = Math.PI * 2;
Math.rand = (min, max) => Math.random() * (max - min) + min;
var myRay;
const WALL_COUNT = 30;
const WALL_STYLE = {radius: 0, lineWidth: 1, strokeStyle: "#000"};
const RAY_STYLE_A = {radius: 2, lineWidth: 1, strokeStyle: "#0FF", fillStyle: "#F00"};
const RAY_STYLE_B = {radius: 5, lineWidth: 3, strokeStyle: "#00F", fillStyle: "#F00"};
const RAY_INTERCEPT_STYLE = {radius: 5, lineWidth: 1, strokeStyle: "#000", fillStyle: "#FF0"};
const ROTATE_RAY = 10; // seconds per rotation
const walls = [];
setTimeout(init, 0);
canvas.addEventListener("click",init);
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
ctx.strokeStyle = strokeStyle;
ctx.fillStyle = fillStyle;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.arc(this.x, this.y, radius, 0, Math.TAU);
ctx.fill();
ctx.stroke();
}
}
class Line {
constructor(start, end) {
this.start = start;
this.end = end;
}
draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
if (radius > 0) {
this.start.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
this.end.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
}
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(this.start.x, this.start.y);
ctx.lineTo(this.end.x, this.end.y);
ctx.stroke();
}
intercept(line) {
var x1, y1, x2, y2, x3, y3, c, u;
x1 = line.end.x - line.start.x;
y1 = line.end.y - line.start.y;
x2 = this.end.x - this.start.x;
y2 = this.end.y - this.start.y;
c = x1 * y2 - y1 * x2;
if (c) {
x3 = line.start.x - this.start.x;
y3 = line.start.y - this.start.y;
u = (x1 * y3 - y1 * x3) / c;
if (u >= 0 && u <= 1) {
u = (x2 * y3 - y2 *x3) / c;
if (u >= 0 && u <= 1) { return [u, line.start.x + x1 * u, line.start.y + y1 * u] }
}
}
}
}
class Ray {
constructor(pos, direction, length) {
this.pos = pos;
this.dir = direction;
this.length = length;
}
draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
this.pos.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(this.pos.x, this.pos.y);
ctx.lineTo(
this.pos.x + Math.cos(this.dir) * this.length,
this.pos.y + Math.sin(this.dir) * this.length
);
ctx.stroke();
}
get end() {
return new Vector(
this.pos.x + Math.cos(this.dir) * this.length,
this.pos.y + Math.sin(this.dir) * this.length
);
}
get line() {
return new Line(this.pos, this.end);
}
cast(lines) {
const tLine = this.line;
var minDist = 1, point;
for (const line of lines) {
const result = line.intercept(tLine);
if (result) {
const [u, x, y] = result;
if (u <= minDist) {
minDist = u;
if (!point) { point = new Vector(x, y) }
else {
point.x = x;
point.y = y;
}
point.u = u;
}
}
}
return point;
}
}
function init() {
walls.length = 0;
for (let i = 0; i < WALL_COUNT / 2; i++) {
walls.push(new Ray(
new Vector(Math.rand(0, canvas.width * 0.4), Math.rand(0, canvas.height)),
(Math.rand(0, 8) | 0) / 4 * Math.PI, 100
).line);
walls.push(new Ray(
new Vector(Math.rand(canvas.width * 0.6, canvas.width), Math.rand(0, canvas.height)),
(Math.rand(0, 8) | 0) / 4 * Math.PI, 100
).line);
}
if(!myRay) {
myRay = new Ray(new Vector(canvas.width / 2, canvas.height / 2), 0, Math.max(canvas.width, canvas.height) * 0.485);
requestAnimationFrame(mainLoop);
}
}
function mainLoop(time) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
myRay.dir = (time / (ROTATE_RAY * 1000)) * Math.TAU;
const point = myRay.cast(walls)
myRay.draw(ctx, RAY_STYLE_A);
for(const w of walls) { w.draw(ctx, WALL_STYLE) }
if (point) {
const len = myRay.length;
myRay.length = point.u * len;
myRay.draw(ctx, RAY_STYLE_B);
myRay.length = len;
point.draw(ctx, RAY_INTERCEPT_STYLE);
}
requestAnimationFrame(mainLoop);
}
#canvas {
border: 2px solid black;
}
<canvas id="canvas" width="500" height="500"> </canvas>
Related
I'm a newbie with p5.js, but I want to create a blob that follows my cursor across the screen. I saw that you could do it with the mouseX and mouseY variables. Here is my code:
let kMax;
let step;
let n = 100; // number of blobs
let radius = -50; // diameter of the circle
let inter = 0.1; // difference between the sizes of two blobs
let maxNoise = 1100;
let lapse = 0; 10 // timer
let mouseX;
let mouseY;
let noiseProg = (x) => (x);
function setup() {
createCanvas(windowWidth, windowHeight);
//colorMode(HSB, 1);
angleMode(DEGREES);
noFill();
//noLoop();
kMax = random(0.6, 1.0);
step = 0.01;
noStroke();
mouseX
mouseY
}
function draw() {
blendMode(BLEND);
background(5,4,0);
blendMode(LIGHTEST);
let t = frameCount/100;
for (let i = n; i > 0; i--) {
let alpha = pow(1 - noiseProg(i / n), 3);
let size = radius + i * inter;
let k = kMax * sqrt(i/n);
let noisiness = maxNoise * noiseProg(i / n);
fill(142, 0, 255, alpha*255);
blob(size, width/2, height/2, k, t - i * step, noisiness, mouseX, mouseY, pmouseX, pmouseY);
fill (255,0,255, alpha*255);
blob(size, width/2, height/2, k, t - i * step + 1, noisiness, mouseX, mouseY, pmouseX, pmouseY);
fill (171, 126, 255, alpha*255);
blob(size, width/2, height/2, k, t - i * step + 2, noisiness, mouseX, mouseY, pmouseX, pmouseY);
//fill (03, 25, 250, alpha*255); aqua blue
//blob(size, width/2, height/2, k, t - i * step + 1, noisiness);
// fill(150, 0, 255, alpha*255);
// blob(size, width/2, height/2, k, t - i * step + 2, noisiness);
}
}
function blob(size, xCenter, yCenter, k, t, noisiness) {
beginShape();
let angleStep = 360 / 12;
for (let theta = 0; theta <= 360 + 14 * angleStep; theta += angleStep) {
let r1, r2;
/*
if (theta < PI / 2) {
r1 = cos(theta);
r2 = 1;
} else if (theta < PI) {
r1 = 0;
r2 = sin(theta);
} else if (theta < 3 * PI / 2) {
r1 = sin(theta);
r2 = 0;
} else {
r1 = 1;
r2 = cos(theta);
}
*/
r1 = cos(theta)+1;
r2 = sin(theta)+1;
let r = size + noise(k * r1, k * r2, t) * noisiness;
let x = xCenter + r * cos(theta);
let y = yCenter + r * sin(theta);
curveVertex(x, y);
}
endShape();
}
I've been trying different options, but currently, it is not working, and I cannot figuring why. When I try with the ellipse function it seems to work ok. Can you help me? :(
I'm given an array of points for a rectangle, sorted from top-left position onwards to each corner clockwise. I need to find the start and end points for a line of each specific corner length from the corner to each side of the rectangle. My code is rotating the points by some random angle just to illustrate the problem but in my actual scenario I only have the corner points and need the start and end points to map out each corner. I'm currently offsetting each point as if the point using directional value array that match to the order of the points in the array when the rectangle isn't rotated, but it doesn't work because the points can be rotated at which point this order also changes. There's an application of sine and cosine that I fail to grasp I'm sure. How can I get the value of each corner point offset by a given length towards each of its sides aligned so as to everything rotates together uniformly?
function rotate(cx, cy, x, y, angle) {
var radians = (Math.PI / 180) * angle,
cos = Math.cos(radians),
sin = Math.sin(radians),
nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return {x: nx, y: ny};
}
const colors =["red","blue", "green","black"];
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let centroid = {};
let points = [{x:30,y:30},{x:110, y:30},{x:110,y:110},{x:30, y:110}];
function update() {
const randAngle = Math.random() * 180 * (Math.random() > 0.5 ? -1:1);
const length = points.length;
centroid = points.reduce((last, current)=> {
last.x += current.x / length;
last.y += current.y / length;
return last;
}, {x: 0, y:0});
points =
points.map(point=> rotate(centroid.x,
centroid.y,
point.x,
point.y,
randAngle));
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw centroid
ctx.beginPath();
ctx.arc(centroid.x, centroid.y, 4, 0, Math.PI * 2);
ctx.stroke();
// draw Square
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for(let i=1;i<points.length;i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.closePath();
ctx.stroke();
// draw corner points
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
ctx.arc(points[i].x, points[i].y, 3, 0, Math.PI * 2);
ctx.fill();
}
const cornerLength = 10;
const startPointDirections = [{ x: 0, y: 1 },
{ x: -1, y: 0 },
{ x: 0, y: -1 },
{ x: 1, y: 0 }];
const endPointDirections = [{ x: 1, y: 0 },
{ x: 0, y: 1 },
{ x: -1, y: 0 },
{ x: 0, y: -1 }];
// draw corner start points and endpoints
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
ctx.arc(startPointDirections[i].x * cornerLength + points[i].x, startPointDirections[i].y * cornerLength + points[i].y, 3, 0, Math.PI * 2);
ctx.arc(endPointDirections[i].x * cornerLength + points[i].x, endPointDirections[i].y * cornerLength + points[i].y, 3, 0, Math.PI * 2);
ctx.fill();
}
}
setInterval(()=> {
update();
draw();
}, 2000);
<canvas></canvas>
Seems you need to apply your rotate function to offset points relative to rotated corner, but you have no angle value at this moment. So you can just get direction vectors from rotated sides.
function rotate(cx, cy, x, y, angle) {
var radians = (Math.PI / 180) * angle,
cos = Math.cos(radians),
sin = Math.sin(radians),
nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return {x: nx, y: ny};
}
const colors =["red","blue", "green","black"];
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let centroid = {};
let points = [{x:30,y:30},{x:110, y:30},{x:110,y:110},{x:30, y:110}];
function update() {
const randAngle = Math.random() * 180 * (Math.random() > 0.5 ? -1:1);
const length = points.length;
centroid = points.reduce((last, current)=> {
last.x += current.x / length;
last.y += current.y / length;
return last;
}, {x: 0, y:0});
points =
points.map(point=> rotate(centroid.x,
centroid.y,
point.x,
point.y,
randAngle));
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw centroid
ctx.beginPath();
ctx.arc(centroid.x, centroid.y, 4, 0, Math.PI * 2);
ctx.stroke();
// draw Square
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for(let i=1;i<points.length;i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.closePath();
ctx.stroke();
// draw corner points
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
ctx.arc(points[i].x, points[i].y, 3, 0, Math.PI * 2);
ctx.fill();
}
const cornerLength = 10;
// draw corner start points and endpoints
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
let dx = points[i].x - points[(i+3)%4].x //previous index in cyclic manner
let dy = points[i].y - points[(i+3)%4].y
let len = Math.sqrt(dx*dx+dy*dy) //really you know side length so use known value
dx = cornerLength * dx/len //normalized vector multiplied by magnitude
dy = cornerLength * dy/len
startx = points[i].x + dx
starty = points[i].y + dy
endx = points[i].x + dy //here apply rotated by -Pi/2 offset vector
endy = points[i].y - dx
ctx.arc(startx, starty, 3, 0, Math.PI * 2);
ctx.arc(endx, endy, 3, 0, Math.PI * 2);
ctx.fill();
}
}
setInterval(()=> {
update();
draw();
}, 2000);
<canvas></canvas>
(Really dx/len and dy/len are equal to cos(angle) and sin(angle) or vice versa, with different combinations of signs)
I have a Rectangle class for drawing to HTML Canvas. It has a rotation property that gets applied in its draw method. If the user drags within the canvas, a selection marquee is being drawn. How can I set the Rectangle's active attribute to true when the Rectangle is within the selection marquee using math? This is a problem I'm having in another language & context so I do not have all of Canvas' methods available to me there (e.g. isPointInPath).
I found a StackOverflow post about finding Mouse position within rotated rectangle in HTML5 Canvas, which I am implementing in the Rectangle method checkHit. It doesn't account for the selection marquee, however. It's just looking at the mouse X & Y, which is still off. The light blue dot is the origin around which the rectangle is being rotated. Please let me know if anything is unclear. Thank you.
class Rectangle
{
constructor(x, y, width, height, rotation) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.xOffset = this.x + this.width/2;
this.yOffset = this.y + ((this.y+this.height)/2);
this.rotation = rotation;
this.active = false;
}
checkHit()
{
// translate mouse point values to origin
let originX = this.xOffset;
let originY = this.yOffset;
let dx = marquee[2] - originX;
let dy = marquee[3] - originY;
// distance between the point and the center of the rectangle
let h1 = Math.sqrt(dx*dx + dy*dy);
let currA = Math.atan2(dy,dx);
// Angle of point rotated around origin of rectangle in opposition
let newA = currA - this.rotation;
// New position of mouse point when rotated
let x2 = Math.cos(newA) * h1;
let y2 = Math.sin(newA) * h1;
// Check relative to center of rectangle
if (x2 > -0.5 * this.width && x2 < 0.5 * this.width && y2 > -0.5 * this.height && y2 < 0.5 * this.height){
this.active = true;
} else {
this.active = false;
}
}
draw()
{
ctx.save();
ctx.translate(this.xOffset, this.yOffset);
ctx.fillStyle = 'rgba(255,255,255,1)';
ctx.beginPath();
ctx.arc(0, 0, 3, 0, 2 * Math.PI, true);
ctx.fill();
ctx.rotate(this.rotation * Math.PI / 180);
ctx.translate(-this.xOffset, -this.yOffset);
if (this.active)
{
ctx.fillStyle = 'rgba(255,0,0,0.5)';
} else {
ctx.fillStyle = 'rgba(0,0,255,0.5)';
}
ctx.beginPath();
ctx.fillRect(this.x, this.y, this.width, this.y+this.height);
ctx.closePath();
ctx.stroke();
ctx.restore();
}
}
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var raf;
var rect = new Rectangle(50,50,90,30,45);
var marquee = [-3,-3,-3,-3];
var BB=canvas.getBoundingClientRect();
var offsetX=BB.left;
var offsetY=BB.top;
var start_x,start_y;
let draw = () => {
ctx.clearRect(0,0, canvas.width, canvas.height);
//rect.rotation+=1;
rect.draw();
ctx.fillStyle = "rgba(200, 200, 255, 0.5)";
ctx.fillRect(parseInt(marquee[0]),parseInt(marquee[1]),parseInt(marquee[2]),parseInt(marquee[3]))
ctx.strokeStyle = "white"
ctx.lineWidth = 1;
ctx.rect(parseInt(marquee[0]),parseInt(marquee[1]),parseInt(marquee[2]),parseInt(marquee[3]))
ctx.stroke()
raf = window.requestAnimationFrame(draw);
}
let dragStart = (e) =>
{
start_x = parseInt(e.clientX-offsetX);
start_y = parseInt(e.clientY-offsetY);
marquee = [start_x,start_y,0,0];
canvas.addEventListener("mousemove", drag);
}
let drag = (e) =>
{
let mouseX = parseInt(e.clientX-offsetX);
let mouseY = parseInt(e.clientY-offsetY);
marquee[2] = mouseX - start_x;
marquee[3] = mouseY - start_y;
rect.checkHit();
}
let dragEnd = (e) =>
{
marquee = [-10,-10,-10,-10];
canvas.removeEventListener("mousemove", drag);
}
canvas.addEventListener('mousedown', dragStart);
canvas.addEventListener('mouseup', dragEnd);
raf = window.requestAnimationFrame(draw);
body
{
margin:0;
}
#canvas
{
width: 360px;
height: 180px;
border: 1px solid grey;
background-color: grey;
}
<canvas id="canvas" width="360" height="180"></canvas>
Do convex polygons overlap
Rectangles are convex polygons.
Rectangle and marquee each have 4 points (corners) that define 4 edges (lines segments) connecting the points.
This solution works for all convex irregular polygons with 3 or more sides.
Points and edges must be sequential either Clockwise CW of Count Clockwise CCW
Test points
If any point of one poly is inside the other polygon then they must overlap. See example function isInside
To check if point is inside a polygon, get cross product of, edge start to the point as vector, and the edge as a vector.
If all cross products are >= 0 (to the left of) then there is overlap (for CW polygon). If polygon is CCW then if all cross products are <= 0 (to the right of) there is overlap.
It is possible to overlap without any points inside the other poly.
Test Edges
If any of the edges from one poly crosses any of the edges from the other then there must be overlap. The function doLinesIntercept returns true if two line segments intercept.
Complete test
Function isPolyOver(poly1, poly2) will return true if there is overlap of the two polys.
A polygon is defined by a set of Point's and Lines's connecting the points.
The polygon can be irregular, meaning that each edge can be any length > 0
Do not pass polygons with an edge of length === 0 or will not work.
Added
I added the function Rectangle.toPoints that transforms the rectangle and returning a set of 4 points (corners).
Example
Example is a copy of your code working using the above methods.
canvas.addEventListener('mousedown', dragStart);
canvas.addEventListener('mouseup', dragEnd);
requestAnimationFrame(draw);
const Point = (x = 0, y = 0) => ({x, y, set(x,y){ this.x = x; this.y = y }});
const Line = (p1, p2) => ({p1, p2});
const selector = { points: [Point(), Point(), Point(), Point()] }
selector.lines = [
Line(selector.points[0], selector.points[1]),
Line(selector.points[1], selector.points[2]),
Line(selector.points[2], selector.points[3]),
Line(selector.points[3], selector.points[0])
];
const rectangle = { points: [Point(), Point(), Point(), Point()] }
rectangle.lines = [
Line(rectangle.points[0], rectangle.points[1]),
Line(rectangle.points[1], rectangle.points[2]),
Line(rectangle.points[2], rectangle.points[3]),
Line(rectangle.points[3], rectangle.points[0])
];
function isInside(point, points) {
var i = 0, p1 = points[points.length - 1];
while (i < points.length) {
const p2 = points[i++];
if ((p2.x - p1.x) * (point.y - p1.y) - (p2.y - p1.y) * (point.x - p1.x) < 0) { return false }
p1 = p2;
}
return true;
}
function doLinesIntercept(l1, l2) {
const v1x = l1.p2.x - l1.p1.x;
const v1y = l1.p2.y - l1.p1.y;
const v2x = l2.p2.x - l2.p1.x;
const v2y = l2.p2.y - l2.p1.y;
const c = v1x * v2y - v1y * v2x;
if(c !== 0){
const u = (v2x * (l1.p1.y - l2.p1.y) - v2y * (l1.p1.x - l2.p1.x)) / c;
if(u >= 0 && u <= 1){
const u = (v1x * (l1.p1.y - l2.p1.y) - v1y * (l1.p1.x - l2.p1.x)) / c;
return u >= 0 && u <= 1;
}
}
return false;
}
function isPolyOver(p1, p2) { // is poly p2 under any part of poly p1
if (p2.points.some(p => isInside(p, p1.points))) { return true };
if (p1.points.some(p => isInside(p, p2.points))) { return true };
return p1.lines.some(l1 => p2.lines.some(l2 => doLinesIntercept(l1, l2)));
}
const ctx = canvas.getContext("2d");
var dragging = false;
const marquee = [0,0,0,0];
const rotate = 0.01;
var startX, startY, hasSize = false;
const BB = canvas.getBoundingClientRect();
const offsetX = BB.left;
const offsetY = BB.top;
class Rectangle {
constructor(x, y, width, height, rotation) {
this.x = x;
this.y = y;
this.height = height;
this.width = width;
this.rotation = rotation;
this.active = false;
}
toPoints(points = [Point(), Point(), Point(), Point()]) {
const xAx = Math.cos(this.rotation) / 2;
const xAy = Math.sin(this.rotation) / 2;
const x = this.x, y = this.y;
const w = this.width, h = this.height;
points[0].set(-w * xAx + h * xAy + x, -w * xAy - h * xAx + y);
points[1].set( w * xAx + h * xAy + x, w * xAy - h * xAx + y);
points[2].set( w * xAx - h * xAy + x, w * xAy + h * xAx + y);
points[3].set(-w * xAx - h * xAy + x, -w * xAy + h * xAx + y);
}
draw() {
ctx.setTransform(1, 0, 0, 1, this.x, this.y);
ctx.fillStyle = 'rgba(255,255,255,1)';
ctx.strokeStyle = this.active ? 'rgba(255,0,0,1)' : 'rgba(0,0,255,1)';
ctx.lineWidth = this.active ? 3 : 1;
ctx.beginPath();
ctx.arc(0, 0, 3, 0, 2 * Math.PI, true);
ctx.fill();
ctx.rotate(this.rotation);
ctx.beginPath();
ctx.rect(-this.width / 2, - this.height / 2, this.width, this.height);
ctx.stroke();
}
}
function draw(){
rect.rotation += rotate;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
rect.draw();
drawSelector();
requestAnimationFrame(draw);
}
function drawSelector() {
if (dragging && hasSize) {
rect.toPoints(rectangle.points);
rect.active = isPolyOver(selector, rectangle);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = "rgba(200, 200, 255, 0.5)";
ctx.strokeStyle = "white";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.rect(...marquee);
ctx.fill();
ctx.stroke();
} else {
rect.active = false;
}
}
function dragStart(e) {
startX = e.clientX - offsetX;
startY = e.clientY - offsetY;
drag(e);
canvas.addEventListener("mousemove", drag);
}
function drag(e) {
dragging = true;
const x = e.clientX - offsetX;
const y = e.clientY - offsetY;
const left = Math.min(startX, x);
const top = Math.min(startY, y);
const w = Math.max(startX, x) - left;
const h = Math.max(startY, y) - top;
marquee[0] = left;
marquee[1] = top;
marquee[2] = w;
marquee[3] = h;
if (w > 0 || h > 0) {
hasSize = true;
selector.points[0].set(left, top);
selector.points[1].set(left + w, top);
selector.points[2].set(left + w, top + h);
selector.points[3].set(left , top + h);
} else {
hasSize = false;
}
}
function dragEnd(e) {
dragging = false;
rect.active = false;
canvas.removeEventListener("mousemove", drag);
}
const rect = new Rectangle(canvas.width / 2, canvas.height / 2, 90, 90, Math.PI / 4);
body
{
margin:0;
}
#canvas
{
width: 360px;
height: 180px;
border: 1px solid grey;
background-color: grey;
}
<canvas id="canvas" width="360" height="180"></canvas>
// Initial Setup
const canvas = document.querySelector('canvas');
const c = canvas.getContext('2d');
// Canvas size
canvas.width = innerWidth;
canvas.height = innerHeight;
//Color
const colors = [
{r: 51, g: 99, b: 252}
//{r: 77, g: 57, b: 206},
// {r: 0, g: 189, b: 255},
];
// Utility Functions
function randomIntFromRange(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function randomColor(colors) {
return colors[Math.floor(Math.random() * colors.length)];
}
function distance(x1, y1, x2, y2) {
const xDist = x2 - x1;
const yDist = y2 - y1;
return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2));
}
function rotateVelocities(velocity, theta) {
const rotatedVelocity = {
x: velocity.x * Math.cos(theta) - velocity.y * Math.sin(theta),
y: velocity.x * Math.sin(theta) + velocity.y * Math.cos(theta)
};
return rotatedVelocity;
}
// Objects
function Particle(x, y, radius, rgb) {
this.x = x;
this.y = y;
this.velocity = {
x: (Math.random() - 0.5) * 3,
y: (Math.random() - 0.5) * 3
};
this.radius = radius;
this.mass = 1;
this.opacity = 0;
this.r = rgb.r;
this.g = rgb.g;
this.b = rgb.b;
this.update = particles => {
this.draw();
for (let i = 0; i < particles.length; i++) {
const otherParticle = particles[i];
if (this.x === otherParticle.x) continue;
if (distance(this.x, this.y, otherParticle.x, otherParticle.y) - this.radius * 2 < 0) {
const res = {
x: this.velocity.x - otherParticle.velocity.x,
y: this.velocity.y - otherParticle.velocity.y
};
if (res.x * (otherParticle.x - this.x) + res.y * (otherParticle.y - this.y) >= 0) {
const m1 = this.mass;
const m2 = otherParticle.mass;
const theta = -Math.atan2(otherParticle.y - this.y, otherParticle.x - this.x);
const rotatedVelocity1 = rotateVelocities(this.velocity, theta);
const rotatedVelocity2 = rotateVelocities(otherParticle.velocity, theta);
const swapVelocity1 = { x: rotatedVelocity1.x * (m1 - m2) / (m1 + m2) + rotatedVelocity2.x * 2 * m2 / (m1 + m2), y: rotatedVelocity1.y };
const swapVelocity2 = { x: rotatedVelocity2.x * (m1 - m2) / (m1 + m2) + rotatedVelocity1.x * 2 * m2 / (m1 + m2), y: rotatedVelocity2.y };
const u1 = rotateVelocities(swapVelocity1, -theta);
const u2 = rotateVelocities(swapVelocity2, -theta);
this.velocity.x = u1.x;
this.velocity.y = u1.y;
otherParticle.velocity.x = u2.x;
otherParticle.velocity.y = u2.y;
}
}
if (distance(this.x, this.y, otherParticle.x, otherParticle.y) - this.radius * 2 < 0 ) {
this.opacity = 0.1;
}
}
if (this.x + this.radius >= canvas.width || this.x - this.radius <= 0)
this.velocity.x = -this.velocity.x;
if (this.y + this.radius >= canvas.height || this.y - this.radius <= 0)
this.velocity.y = -this.velocity.y;
this.x += this.velocity.x;
this.y += this.velocity.y;
};
this.draw = () => {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.strokeStyle = `rgba(${this.r}, ${this.g}, ${this.b}, 1)`;
c.stroke();
c.fillStyle = `rgba(${this.r}, ${this.g}, ${this.b}, ${this.opacity}`;
c.fill();
c.closePath();
};
}
// Implementation
let particles;
function init() {
particles = [];
let radius = 80
for (let i = 0; i < 10; i++) {
let x = randomIntFromRange(radius, innerWidth - radius);
let y = randomIntFromRange(radius, innerHeight - radius);
if (particles.length >= 1) {
for (let j = 0; j < particles.length; j++) {
if (distance(x, y, particles[j].x, particles[j].y) - radius * 2 < 0) {
x = randomIntFromRange(radius, innerWidth - radius);
y = randomIntFromRange(radius, innerHeight - radius);
j = -1;
continue;
}
}
}
particles.push(new Particle(x, y, radius, randomColor(colors)));
}
}
//I would like to initiate one circle out of all as red and whenever the red circle collide with other circle, other circle got red and at last all circle become red. but when two white circle collide it should not become red. basically one infected circle making other circle infected by touching.
I'm working on a custom button animation for my Wix website it is written in JavaScript and I get this error when I plug the code into an onclick event:
"cannot read property 'requestAnimationFrame' of undefined"
I've tried removing the return and I think the problem has to do with some sort of iframe issue because the original code was built to run in an html <iframe> element but that dident end up working out.
If someone could rewrite this code so that it dosent give this error that would be super helpful.
export function button7_click(event) {
$w.window.requestAnimFrame = (function() {
return $w.window.requestAnimationFrame ||
$w.window.webkitRequestAnimationFrame ||
$w.window.mozRequestAnimationFrame ||
$w.window.oRequestAnimationFrame ||
$w.window.msRequestAnimationFrame ||
function(callback) {
$w.window.setTimeout(callback, 1000 / 60);
};
})();
Math.randMinMax = function(min, max, round) {
var val = min + (Math.random() * (max - min));
if (round) val = Math.round(val);
return val;
};
Math.TO_RAD = Math.PI / 180;
Math.getAngle = function(x1, y1, x2, y2) {
var dx = x1 - x2,
dy = y1 - y2;
return Math.atan2(dy, dx);
};
Math.getDistance = function(x1, y1, x2, y2) {
var xs = x2 - x1,
ys = y2 - y1;
xs *= xs;
ys *= ys;
return Math.sqrt(xs + ys);
};
var FX = {};
(function() {
var canvas = $w.document.getElementById('myCanvas'),
ctx = canvas.getContext('2d'),
lastUpdate = new Date(),
mouseUpdate = new Date(),
lastMouse = [],
width, height;
FX.particles = [];
setFullscreen();
$w.document.getElementById('button').addEventListener('mousedown', buttonEffect);
function buttonEffect() {
var button = $w.document.getElementById('button'),
height = button.offsetHeight,
left = button.offsetLeft,
top = button.offsetTop,
width = button.offsetWidth,
x, y, degree;
for (var i = 0; i < 40; i = i + 1) {
if (Math.random() < 0.5) {
y = Math.randMinMax(top, top + height);
if (Math.random() < 0.5) {
x = left;
degree = Math.randMinMax(-45, 45);
} else {
x = left + width;
degree = Math.randMinMax(135, 225);
}
} else {
x = Math.randMinMax(left, left + width);
if (Math.random() < 0.5) {
y = top;
degree = Math.randMinMax(45, 135);
} else {
y = top + height;
degree = Math.randMinMax(-135, -45);
}
}
createParticle({
x: x,
y: y,
degree: degree,
speed: Math.randMinMax(100, 150),
vs: Math.randMinMax(-4, -1)
});
}
}
$w.window.setTimeout(buttonEffect, 100);
loop();
$w.window.addEventListener('resize', setFullscreen);
function createParticle(args) {
var options = {
x: width / 2,
y: height / 2,
color: 'hsla(' + Math.randMinMax(160, 290) + ', 100%, 50%, ' + (Math.random().toFixed(2)) + ')',
degree: Math.randMinMax(0, 360),
speed: Math.randMinMax(300, 350),
vd: Math.randMinMax(-90, 90),
vs: Math.randMinMax(-8, -5)
};
for ($w.key in args) {
options[$w.key] = args[$w.key];
}
FX.particles.push(options);
}
function loop() {
var thisUpdate = new Date(),
delta = (lastUpdate - thisUpdate) / 1000,
amount = FX.particles.length,
size = 2,
i = 0,
p;
ctx.fillStyle = 'rgba(15,15,15,0.25)';
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeStyle = 'lighter';
for (; i < amount; i = i + 1) {
p = FX.particles[i];
p.degree += (p.vd * delta);
p.speed += (p.vs); // * delta);
if (p.speed < 0) continue;
p.x += Math.cos(p.degree * Math.TO_RAD) * (p.speed * delta);
p.y += Math.sin(p.degree * Math.TO_RAD) * (p.speed * delta);
ctx.save();
ctx.translate(p.x, p.y);
ctx.rotate(p.degree * Math.TO_RAD);
ctx.fillStyle = p.color;
ctx.fillRect(-size, -size, size * 2, size * 2);
ctx.restore();
}
lastUpdate = thisUpdate;
$w.requestAnimFrame(loop);
}
function setFullscreen() {
width = canvas.width = $w.window.innerWidth;
height = canvas.height = $w.window.innerHeight;
};
})();
}
The button once clicked should run the JavaScript code and play the animation. Thanks for your help.