I have the code that draw a point inside a polygon. Each time I change value on textbox for x & y, it will draw a new point and still keep old points on my polygon, so I want to ask how can I clear all old points ?
I already try to remove old tag and create new each time draw a new point but it's not ok. If you know about this, pls help. Thanks
Canvas = function(){ //v1.0
var o = this;
(o.penPos = {x: 0, y: 0}, o.pixelSize = 10, o.pen = {style: "solid",
size: 1, color: "#000"}, o.brush = {style: "solid", color: "#000"});
};
with({p: Canvas.prototype}){
p.pixel = function(x, y, color) {
var o = this, s = document.body.appendChild(document.createElement("div")).style;
//alert ("top"+(y * o.pixelSize) + "px");
return (s.position = "absolute", s.width = (o.pen.size * o.pixelSize) + "px",
s.height = (o.pen.size * o.pixelSize) + "px", s.fontSize = "1px",
s.left = (x * o.pixelSize) + "px", s.top = (y * o.pixelSize) + "px",
s.backgroundColor = color || o.pen.color, o);
};
p.line = function(x1, y1, x2, y2){
if(Math.abs(x1 - x2) < Math.abs(y1 - y2))
for(y = Math.min(y1, y2) - 1, x = Math.max(y1, y2); ++y <= x;
this.pixel((y * (x1 - x2) - x1 * y2 + y1 * x2) / (y1 - y2), y));
else
for(x = Math.min(x1, x2) - 1, y = Math.max(x1, x2); ++x <= y;
this.pixel(x, (x * (y1 - y2) - y1 * x2 + x1 * y2) / (x1 - x2)));
return this;
};
p.arc = function(x, y, raio, startAngle, degrees) {
for(degrees += startAngle; degrees --> startAngle;
this.pixel(Math.cos(degrees * Math.PI / 180) * raio + x,
Math.sin(degrees * Math.PI / 180) * raio + y)); return this;
};
p.rectangle = function(x, y, width, height, rotation){
return this.moveTo(x, y).lineBy(0, height).lineBy(width, 0).lineBy(0, -height).lineBy(-width, 0);
};
p.moveTo = function(x, y){var o = this; return (o.penPos.x = x, o.penPos.y = y, o);};
p.moveBy = function(x, y){var o = this; return o.moveTo(o.penPos.x + x, o.penPos.y + y);};
p.lineTo = function(x, y){var o = this; return o.line(o.penPos.x, o.penPos.y, x, y).moveTo(x, y);};
p.lineBy = function(x, y){var o = this; return o.lineTo(o.penPos.x + x, o.penPos.y + y);};
p.curveTo = function(cx, cy, x, y){};
p.polyBezier = function(points){};
p.path = function(points){};
}
function isPointInPoly(poly, pt){
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((poly[i].y <= pt.y && pt.y < poly[j].y) || (poly[j].y <= pt.y && pt.y < poly[i].y))
&& (pt.x < (poly[j].x - poly[i].x) * (pt.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
&& (c = !c);
return c;
}
var length = 50,
points = [
{x: 35, y:10422},
{x: 36, y:32752},
{x: 40, y:35752},
{x: 55, y:27216},
{x: 59, y:29319},
{x: 58, y:10411}
];
var canvas = new Canvas;
canvas.pen.color = "#f00";
canvas.pixelSize = 1;
canvas.moveTo(getx(points[points.length-1].x) , gety(points[points.length-1].y));
for(var i = points.length; i--; canvas.lineTo(getx( points[i].x), gety(points[i].y)));
function draw(){
var x=38; var y=10433;
canvas.pixel(getx(x),gety(y));
alert(isPointInPoly(points, {x: x,y: y}) ? "In" : "Out");
}
function getx(x){
return Math.round(x*10);
}
function gety(y){
return Math.round(400-y/250);
}
//]]>
You can clear a rectangular section of the canvas by using context.clearRect(x,y,width,height). For your application, I would imagine every time you draw a polygon, you want to clear our your canvas and start over. To do this, simply, call context.clearRect(0,0,canvas.width,canvas.height).
There is also a trick to clearing any polygon shaped region outlined in this SO question.
Related
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>
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.
I'm making a pong style game in JavaScript where the paddle is curved. I already have collision between 2 balls but I seem to be having trouble with the ball and arc.
I've looked at this thread already:
Collision detection of a ball with an arc
But I can't seem to make the answer there work for me. Perhaps because I draw the arc differently.
Here's my variables as well as how the paddle is being drawn onto the canvas. When the player presses a key, the angles for the paddle are incremented so it revolves around the player.
If anyone can help I would appreciate it.
https://i.stack.imgur.com/kz0ZV.png
function Player(name, radius, innerColour, outerColour, x, y)
{
this.prop = {
name: name,
innerColour: innerColour,
outerColour: outerColour
};
this.phys = {
x: x,
y: y,
dx: 0,
dy: 0,
mass: radius ** 3,
radius: radius
};
this.padd = {
innerRadius: 65,
outerRadius: 85,
active: true,
startAngle: 225,
centerAngle: 270,
endAngle: 315,
rotation: false
};
this.draw = function()
{
var inR = this.padd.innerRadius;
var outR = this.padd.outerRadius;
var inC = Math.sqrt((inR ** 2) * 2);
var outC = Math.sqrt((outR ** 2) * 2);
var sAng = this.padd.startAngle;
var cAng = this.padd.centerAngle;
var eAng = this.padd.endAngle;
//Draw paddle
ctx.beginPath();
ctx.moveTo(this.rotatePoint(inR, sAng, "x"), this.rotatePoint(inR, sAng, "y"));
ctx.arcTo (this.rotatePoint(inC, cAng, "x"), this.rotatePoint(inC, cAng, "y"),
this.rotatePoint(inR, eAng, "x"), this.rotatePoint(inR, eAng, "y"), inR);
ctx.lineTo(this.rotatePoint(outR, eAng, "x"), this.rotatePoint(outR, eAng, "y"))
ctx.arcTo (this.rotatePoint(outC, cAng, "x"), this.rotatePoint(outC, cAng, "y"),
this.rotatePoint(outR, sAng, "x"), this.rotatePoint(outR, sAng, "y"), outR);
ctx.lineTo(this.rotatePoint(inR, sAng, "x"), this.rotatePoint(inR, sAng, "y"));
ctx.fillStyle = this.prop.outerColour;
ctx.fill();
ctx.closePath();
};
this.rotatePoint = function(radius, angle, axis)
{
var x = this.phys.x;
var y = this.phys.y;
var radians = angle * (Math.PI / 180.0);
var x1 = x + radius;
var newX = Math.cos(radians) * (x1 - x) + x;
var newY = Math.sin(radians) * (x1 - x) + y;
if (axis == "x")
{
return newX;
}
else if (axis == "y")
{
return newY;
}
};
}
Edit: Sorry I forgot to add my attempt at the collision code.
I run it every frame but it doesn't seem to detect when they're colliding.
The objects array is both every ball on the screen and the 2 players, and the players array just contains the 2 players.
//Calculates events, speed and trajectory for paddle collisions
function paddleCollision()
{
for (var obj in objects)
{
for (var player in players)
{
var sAng = players[player].padd.startAngle * (Math.PI / 180.0);
var eAng = players[player].padd.endAngle * (Math.PI / 180.0);
var inR = players[player].padd.innerRadius;
var outR = players[player].padd.outerRadius;
var ballR = objects[obj].phys.radius;
var collides = false;
var dX = objects[obj].phys.x - players[player].phys.x;
var dY = objects[obj].phys.y - players[player].phys.y;
var dist = Math.sqrt((dX ** 2) + (dY ** 2));
var dir = Math.atan2(dY, dX);
var tanAng = Math.asin(ballR / dist);
var dir0 = dir + tanAng;
var dir1 = dir - tanAng;
if (dist + ballR > inR && dist - ballR < outR)
{
var d = dir > sAng && dir < eAng;
var d0 = dir0 > sAng && dir0 < eAng;
var d1 = dir1 > sAng && dir1 < eAng;
if (d || d0 && d1)
{
collides = true;
}
else if (d0 != d1)
{
var x0 = players[player].phys.x + outR * Math.cos(sAng) - objects[obj].phys.x;
var y0 = players[player].phys.y + outR * Math.sin(sAng) - objects[obj].phys.y;
var x1 = players[player].phys.x + outR * Math.cos(eAng) - objects[obj].phys.x;
var y1 = players[player].phys.y + outR * Math.sin(eAng) - objects[obj].phys.y;
if ((x0 ** 2) + (y0 ** 2) < (ballR ** 2) || (x1 ** 2) + (y1 ** 2) < (ballR ** 2))
{
collides = true;
}
}
}
}
}
if (collides)
{
console.log("HITTING");
}
}
This worked for me:
function arcsCollision(first, second) {
const dx = first.x - second.x;
const dy = first.y - second.y;
const distance = Math.sqrt(dx**2 + dy**2);
return (
distance
<=
(first.radius + second.radius + 0.1)
);
}
function arcAndRectCollision(arc, rect) {
return (
arc.x - arc.radius < rect.x ||
arc.x + arc.radius > rect.width ||
arc.y - arc.radius < rect.y ||
arc.y + arc.radius > rect.height
);
}
You can go to this website for more info.
https://spicyyoghurt.com/tutorials/html5-javascript-game-development/collision-detection-physics
I need to detect collision circle with any line. I have array with verticles of polygon (x, y) and draw this polygon in loop. For detection I use algorithm, which calculate triangle height. Then I check if this height < 0, then circle collided with line.
The picture, that describe this method:
But I have unexpected result. My circle collide with transparent line (what?). I can't explain how it happens.
Demo at jsfiddle: https://jsfiddle.net/f458rdz6/1/
Function, which check the collisions and response it:
var p = polygonPoints;
for (var i = 0, n = p.length; i < n; i++) {
var start = i;
var end = (i + 1) % n;
var x0 = p[start].x;
var y0 = p[start].y;
var x1 = p[end].x;
var y1 = p[end].y;
// detection collision
var dx = x1 - x0;
var dy = y1 - y0;
var len = Math.sqrt(dx * dx + dy * dy);
var dist = (dx * (this.y - y0) - dy * (this.x - x0)) / len;
if (dist < this.radius) {
continue;
}
// calculate reflection, because collided
var wallAngle = Math.atan2(dy, dx);
var wallNormalX = Math.sin(wallAngle);
var wallNormalY = -Math.cos(wallAngle);
var d = 2 * (this.velocityX * wallNormalX + this.velocityY * wallNormalY);
this.x -= d * wallNormalX;
this.y -= d * wallNormalY;
}
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var polygonPoints = [
{
x: 240,
y: 130
},
{
x: 140,
y: 100
},
{
x: 180,
y: 250
},
{
x: 320,
y: 280
},
{
x: 400,
y: 50
}
];
var game = {
ball: new Ball()
};
function Ball() {
this.x = canvas.width / 2;
this.y = canvas.height - 100;
this.oldX = this.x - 1;
this.oldY = this.y + 1;
this.velocityX = 0;
this.velocityY = 0;
this.radius = 8;
};
Ball.prototype.draw = function() {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fillStyle = '#0095DD';
ctx.fill();
ctx.closePath();
};
Ball.prototype.update = function() {
var x = this.x;
var y = this.y;
this.velocityX = this.x - this.oldX;
this.velocityY = this.y - this.oldY;
this.x += this.velocityX;
this.y += this.velocityY;
this.oldX = x;
this.oldY = y;
};
Ball.prototype.collision = function() {
var p = polygonPoints;
for (var i = 0, n = p.length; i < n; i++) {
var start = i;
var end = (i + 1) % n;
var x0 = p[start].x;
var y0 = p[start].y;
var x1 = p[end].x;
var y1 = p[end].y;
// detection collision
var dx = x1 - x0;
var dy = y1 - y0;
var len = Math.sqrt(dx * dx + dy * dy);
var dist = (dx * (this.y - y0) - dy * (this.x - x0)) / len;
if (dist < this.radius) {
continue;
}
// calculate reflection, because collided
var wallAngle = Math.atan2(dy, dx);
var wallNormalX = Math.sin(wallAngle);
var wallNormalY = -Math.cos(wallAngle);
var d = 2 * (this.velocityX * wallNormalX + this.velocityY * wallNormalY);
this.x -= d * wallNormalX;
this.y -= d * wallNormalY;
}
};
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function drawPolygon() {
ctx.beginPath();
ctx.strokeStyle = '#333';
ctx.moveTo(polygonPoints[0].x, polygonPoints[0].y);
for (var i = 1, n = polygonPoints.length; i < n; i++) {
ctx.lineTo(polygonPoints[i].x, polygonPoints[i].y);
}
ctx.lineTo(polygonPoints[0].x, polygonPoints[0].y);
ctx.stroke();
ctx.closePath();
}
function render() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawPolygon();
game.ball.draw();
game.ball.update();
game.ball.collision();
window.requestAnimationFrame(render);
}
render();
canvas {
border: 1px solid #333;
}
<canvas id="myCanvas" width="480" height="320"></canvas>
What the problem? Maybe I need use other method for detect collision? I tried to use this one, but if my circle has high speed this method not working.
Thank you.
Circle line segment intercept
UPDATE
This answer includes line line intercept, moving a line along its normal, distance point (circle) to line, and circle line intercept.
The circle is
var circle = {
radius : 500,
center : point(1000,1000),
}
The line segment is
var line = {
p1 : point(500,500),
p2 : point(2000,1000),
}
A point is
var point = {
x : 100,
y : 100,
}
Thus the function to find the intercept of a line segment width a circle
The function returns an array of up to two point on the line segment. If no points found returns an empty array.
function inteceptCircleLineSeg(circle, line){
var a, b, c, d, u1, u2, ret, retP1, retP2, v1, v2;
v1 = {};
v2 = {};
v1.x = line.p2.x - line.p1.x;
v1.y = line.p2.y - line.p1.y;
v2.x = line.p1.x - circle.center.x;
v2.y = line.p1.y - circle.center.y;
b = (v1.x * v2.x + v1.y * v2.y);
c = 2 * (v1.x * v1.x + v1.y * v1.y);
b *= -2;
d = Math.sqrt(b * b - 2 * c * (v2.x * v2.x + v2.y * v2.y - circle.radius * circle.radius));
if(isNaN(d)){ // no intercept
return [];
}
u1 = (b - d) / c; // these represent the unit distance of point one and two on the line
u2 = (b + d) / c;
retP1 = {}; // return points
retP2 = {}
ret = []; // return array
if(u1 <= 1 && u1 >= 0){ // add point if on the line segment
retP1.x = line.p1.x + v1.x * u1;
retP1.y = line.p1.y + v1.y * u1;
ret[0] = retP1;
}
if(u2 <= 1 && u2 >= 0){ // second add point if on the line segment
retP2.x = line.p1.x + v1.x * u2;
retP2.y = line.p1.y + v1.y * u2;
ret[ret.length] = retP2;
}
return ret;
}
UPDATE
Line line intercept.
Returns a point if found else returns undefined.
function interceptLines(line,line1){
var v1, v2, c, u;
v1 = {};
v2 = {};
v3 = {};
v1.x = line.p2.x - line.p1.x; // vector of line
v1.y = line.p2.y - line.p1.y;
v2.x = line1.p2.x - line1.p1.x; //vector of line2
v2.y = line1.p2.y - line1.p1.y;
var c = v1.x * v2.y - v1.y * v2.x; // cross of the two vectors
if(c !== 0){
v3.x = line.p1.x - line1.p1.x;
v3.y = line.p1.y - line1.p1.y;
u = (v2.x * v3.y - v2.y * v3.x) / c; // unit distance of intercept point on this line
return {x : line.p1.x + v1.x * u, y : line.p1.y + v1.y * u};
}
return undefined;
}
Lift Line
Move line along its normal
function liftLine(line,dist){
var v1,l
v1 = {};
v1.x = line.p2.x - line.p1.x; // convert line to vector
v1.y = line.p2.y - line.p1.y;
l = Math.sqrt(v1.x * v1.x + v1.y * v1.y); // get length;
v1.x /= l; // Assuming you never pass zero length lines
v1.y /= l;
v1.x *= dist; // set the length
v1.y *= dist;
// move the line along its normal the required distance
line.p1.x -= v1.y;
line.p1.y += v1.x;
line.p2.x -= v1.y;
line.p2.y += v1.x;
return line; // if needed
}
Distance circle (or point) to a line segment
Returns the closest distance to the line segment. It is just the circle center that I am using. So you can replace circle with a point
function circleDistFromLineSeg(circle,line){
var v1, v2, v3, u;
v1 = {};
v2 = {};
v3 = {};
v1.x = line.p2.x - line.p1.x;
v1.y = line.p2.y - line.p1.y;
v2.x = circle.center.x - line.p1.x;
v2.y = circle.center.y - line.p1.y;
u = (v2.x * v1.x + v2.y * v1.y) / (v1.y * v1.y + v1.x * v1.x); // unit dist of point on line
if(u >= 0 && u <= 1){
v3.x = (v1.x * u + line.p1.x) - circle.center.x;
v3.y = (v1.y * u + line.p1.y) - circle.center.y;
v3.x *= v3.x;
v3.y *= v3.y;
return Math.sqrt(v3.y + v3.x); // return distance from line
}
// get distance from end points
v3.x = circle.center.x - line.p2.x;
v3.y = circle.center.y - line.p2.y;
v3.x *= v3.x; // square vectors
v3.y *= v3.y;
v2.x *= v2.x;
v2.y *= v2.y;
return Math.min(Math.sqrt(v2.y + v2.x), Math.sqrt(v3.y + v3.x)); // return smaller of two distances as the result
}
Edit
Here's a new version which correctly applies the length and model but doesn't position the model correctly. I figured it might help.
http://codepen.io/pixelass/pen/78f9e97579f99dc4ae0473e33cae27d5?editors=001
I have 2 canvas instances
model
result
On the model view the user can drag the handles to modify the model
The result view should then apply the model to every segment (relatively)
This is just a basic l-system logic for fractal curves though I am having problems applying the model to the segments.
Se the picture below: The red lines should replicate the model, but I can't figure out how to correctly apply the logic
I have a demo version here: http://codepen.io/pixelass/pen/c4d7650af7ce4901425b326ad7a4b259
ES6
// simplify Math
'use strict';
Object.getOwnPropertyNames(Math).map(function(prop) {
window[prop] = Math[prop];
});
// add missing math functions
var rad = (degree)=> {
return degree * PI / 180;
};
var deg = (radians)=> {
return radians * 180 / PI;
};
// get our drawing areas
var model = document.getElementById('model');
var modelContext = model.getContext('2d');
var result = document.getElementById('result');
var resultContext = result.getContext('2d');
var setSize = function setSize() {
model.height = 200;
model.width = 200;
result.height = 400;
result.width = 400;
};
// size of the grabbing dots
var dotSize = 5;
// flag to determine if we are grabbing a point
var grab = -1;
// set size to init instances
setSize();
//
var iterations = 1;
// define points
// this only defines the initial model
var returnPoints = function returnPoints(width) {
return [{
x: 0,
y: width
}, {
x: width / 3,
y: width
}, {
x: width / 2,
y: width / 3*2
}, {
x: width / 3 * 2,
y: width
}, {
x: width,
y: width
}];
};
// set initial state for model
var points = returnPoints(model.width);
// handle interaction
// grab points only if hovering
var grabPoint = function grabPoint(e) {
var X = e.layerX;
var Y = e.layerY;
for (var i = 1; i < points.length - 1; i++) {
if (abs(X - points[i].x) < dotSize && abs(Y - points[i].y) < dotSize) {
model.classList.add('grabbing');
grab = i;
}
}
};
// release point
var releasePoint = function releasePoint(e) {
if (grab > -1) {
model.classList.add('grab');
model.classList.remove('grabbing');
}
grab = -1;
};
// set initial state for result
// handle mouse movement on the model canvas
var handleMove = function handleMove(e) {
// determine current mouse position
var X = e.layerX;
var Y = e.layerY;
// clear classes
model.classList.remove('grabbing');
model.classList.remove('grab');
// check if hovering a dot
for (var i = 1; i < points.length - 1; i++) {
if (abs(X - points[i].x) < dotSize && abs(Y - points[i].y) < dotSize) {
// indicate grabbable
model.classList.add('grab');
}
}
// if grabbing
if (grab > -1) {
// indicate grabbing
model.classList.add('grabbing');
// modify dot on the model canvas
points[grab] = {
x: X,
y: Y
};
// modify dots on the result canvas
drawSegment({
x: points[grab - 1].x,
y: points[grab - 1].y
}, {
x: X,
y: Y
});
}
};
let m2 = points[1].x / points[4].x
let m3 = points[2].x / points[4].x
let m4 = points[3].x / points[4].x
let n2 = points[1].y / points[4].y
let n3 = points[2].y / points[4].y
let n4 = points[3].y / points[4].y
var drawSegment = function drawSegment(start, end) {
var dx = end.x - start.x
var dy = end.y - start.y
var dist = sqrt(dx * dx + dy * dy)
var angle = atan2(dy, dx)
let x1 = end.x
let y1 = end.y
let x2 = round(cos(angle) * dist)
let y2 = round(sin(angle) * dist)
resultContext.srtokeStyle = 'red'
resultContext.beginPath()
resultContext.moveTo(x1, y1)
resultContext.lineTo(x2, y2)
resultContext.stroke()
m2 = points[1].x / points[4].x
m3 = points[2].x / points[4].x
m4 = points[3].x / points[4].x
n2 = points[1].y / points[4].y
n3 = points[2].y / points[4].y
n4 = points[3].y / points[4].y
};
var drawDots = function drawDots(points) {
// draw dots
for (var i = 1; i < points.length - 1; i++) {
modelContext.lineWidth = 4; //
modelContext.beginPath();
modelContext.strokeStyle = 'hsla(' + 360 / 5 * i + ',100%,40%,1)';
modelContext.fillStyle = 'hsla(0,100%,100%,1)';
modelContext.arc(points[i].x, points[i].y, dotSize, 0, 2 * PI);
modelContext.stroke();
modelContext.fill();
}
};
var drawModel = function drawModel(ctx, points, n) {
var dx = points[1].x - points[0].x
var dy = points[1].y - points[0].y
var dist = sqrt(dx * dx + dy * dy)
var angle = atan2(dy, dx)
let x1 = points[1].x
let y1 = points[1].y
let x2 = round(cos(angle) * dist)
let y2 = round(sin(angle) * dist)
ctx.strokeStyle = 'hsla(0,0%,80%,1)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(points[0].x,
points[0].y)
ctx.lineTo(points[1].x * m2,
points[1].y * n2)
ctx.lineTo(points[1].x * m3,
points[1].y * n3)
ctx.lineTo(points[1].x * m4,
points[1].y * n4)
ctx.lineTo(points[1].x,
points[1].y)
ctx.stroke();
ctx.strokeStyle = 'hsla(100,100%,80%,1)';
ctx.beginPath();
ctx.moveTo(points[0].x,
points[0].y)
ctx.lineTo(points[1].x,
points[1].y)
ctx.stroke()
if (n > 0 ) {
drawModel(resultContext, [{
x: points[0].x,
y: points[0].y
}, {
x: points[1].x * m2,
y: points[1].y * n2
}], n - 1);
drawModel(resultContext, [{
x: points[1].x * m2,
y: points[1].y * n2
}, {
x: points[1].x * m3,
y: points[1].y * n3
}], n - 1);
/*
drawModel(resultContext, [{
x: points[1].x * m3,
y: points[1].y * m3
}, {
x: points[1].x * m4,
y: points[1].y * n4
}], n - 1);
drawModel(resultContext, [{
x: points[1].x * m4,
y: points[1].y * m4
}, {
x: points[1].x,
y: points[1].y
}], n - 1);*/
} else {
ctx.strokeStyle = 'hsla(0,100%,50%,1)';
ctx.beginPath();
ctx.moveTo(points[0].x,
points[0].y)
ctx.lineTo(points[1].x * m2,
points[1].y * n2)
ctx.lineTo(points[1].x * m3,
points[1].y * n3)
ctx.lineTo(points[1].x * m4,
points[1].y * n4)
ctx.lineTo(points[1].x,
points[1].y)
ctx.stroke();
}
};
var draw = function draw() {
// clear both screens
modelContext.fillStyle = 'hsla(0,0%,100%,.5)';
modelContext.fillRect(0, 0, model.width, model.height);
resultContext.fillStyle = 'hsla(0,0%,100%,1)';
resultContext.fillRect(0, 0, result.width, result.height);
// draw model
drawModel(modelContext, [{
x: 0,
y: 200
}, {
x: 200,
y: 200
}]);
drawModel(resultContext, [{
x: 0,
y: 400
}, {
x: 400,
y: 400
}],iterations);
// draw the dots to indicate grabbing points
drawDots(points);
// redraw
requestAnimationFrame(draw);
};
window.addEventListener('resize', setSize);
model.addEventListener('mousemove', handleMove);
model.addEventListener('mousedown', grabPoint);
window.addEventListener('mouseup', releasePoint);
setSize();
draw();
Write a function to transform a point given the point, an old origin (the start of the model line segment), a new origin (the start of the child line segment), an angle and a scale (you have already calculated these):
var transformPoint = function transformPoint(point, oldOrigin, newOrigin, angle, dist) {
// subtract old origin to rotate and scale relative to it:
var x = point.x - oldOrigin.x;
var y = point.y - oldOrigin.y;
// rotate by angle
var sine = sin(angle)
var cosine = cos(angle)
var rotatedX = (x * cosine) - (y * sine);
var rotatedY = (x * sine) + (y * cosine);
// scale
rotatedX *= dist;
rotatedY *= dist;
// offset by new origin and return:
return {x: rotatedX + newOrigin.x - oldOrigin.x, y: rotatedY + newOrigin.y - oldOrigin.y }
}
You need to translate it by the old origin (so that you can rotate around it), then rotate, then scale, then translate by the new origin. Then return the point.
modelLogic[0] is the old origin because it defines the start of the segment in the model and points[0] is the new origin because that is what it is mapped to by the transformation.
You can call the function from your drawModel function like this:
let p1 = transformPoint(modelLogic[0], modelLogic[0], points[0], angle, dist);
let p2 = transformPoint(modelLogic[1], modelLogic[0], points[0], angle, dist);
let p3 = transformPoint(modelLogic[2], modelLogic[0], points[0], angle, dist);
let p4 = transformPoint(modelLogic[3], modelLogic[0], points[0], angle, dist);
let p5 = transformPoint(modelLogic[4], modelLogic[0], points[0], angle, dist);
and change your drawing code to use the returned points p1, p2 etc instead of x1, y1, x2, y2 etc.
Alternatively, you can create a single matrix to represent all of these translation, rotation and scaling transforms and transform each point by it in turn.