Edit: I could divide the radius with the angle?
Problem: For the sake of learning the arts of collision in HTML5 Canvas, I am currently trying to get a full circle to collide with a segmented circle, in this case a semi circle.
What I Tried: My first thought was a simple circle to circle collision would do but I was wrong. I read various sources on collision detection but all of them were either the standard circle / circle, box / circle, box / box, or polygon collision formulas.
My Question: What is the formula for colliding a full circle with only a segmented circle? It seems something other than just the radius comes into play. Maybe the radians as well?
Attempt:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var C1 = {x: 45, y: 65, radius: 20};
var C2 = {x: 60, y: 20, radius: 20};
var dx = C1.x - C2.x;
var dy = C1.y - C2.y;
var distance = Math.sqrt(dx * dx + dy * dy);
ctx.beginPath();
ctx.arc(C1.x, C1.y, C1.radius, 0, Math.PI * 2);
ctx.fillStyle = 'green';
ctx.fill();
ctx.beginPath();
ctx.rotate(0.3);
ctx.arc(C2.x, C2.y, C2.radius, 0, Math.PI * 1);
ctx.fillStyle = 'red';
ctx.fill();
if (distance < C1.radius + C2.radius) {
alert('true')
}
else {
alert('false')
}
A demo for to play around with: jsfiddle.net/tonyh90g/1
My learning resource: https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection
You're on the right tracks, you will indeed need to not only calculate the distance between centres but also the angle of the intersection point(s) on the segment.
In general cartesian coordinates that angle can be calculated using Math.atan2(dy, dx) where dx and dy are the difference in coordinates from the segment to the circle. If those angles fall between the segment's angle, you have a hit.
[NB: At the exact point of touching there's only one point, but if the two objects end up slightly overlapping, which is not uncommon in animation, you'll get two points].
It's also possible that the circle could intersect the line segments rather than the round portion of the segment, but I think those cases will be correctly caught anyway. This may require further investigation, and if required would need a line-segment / circle intersection test.
I also note in your fiddle that you're using rotate() transforms on the sector. This will foul up your angle calculations unless you specifically account for it. It may be easier to use absolute angles throughout.
Related
I have an instance of HTML 5 canvas and a rectangle drawn on it.
My drawing function takes a resizing angle into account and uses relative coordinates.
Relative coordinates're based upon three variables: top left rectangle point, rectangle width and rectangle height.
Rectangle width and rectangle height're calculated using two points: top left rectangle point and bottom right rectangle point.
To sum up, drawing function depends on top left rectangle point, bottom right rectangle point and rotation. It's an important point for the following text!
Here's a code snippet:
var canvas = document.getElementById('imageCanvas');
var ctx = canvas.getContext('2d');
var xTopLeft = 550;
var yTopLeft = 200;
var xBottomRight = 750;
var yBottomRight = 450;
var w = Math.max(xTopLeft, xBottomRight) - Math.min(xTopLeft, xBottomRight);
var h = Math.max(yTopLeft, yBottomRight) - Math.min(yTopLeft, yBottomRight);
var r = 1;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save()
ctx.translate(xTopLeft + w / 2, yTopLeft + h / 2);
ctx.rotate(r);
ctx.fillStyle = "yellow";
ctx.fillRect(w / 2 * (-1), h / 2 * (-1), w, h);
ctx.restore()
}
Here's my rectangle with a bunch of controls: eight resizing handles (white) and one rotation handle (green):
Rotating works fine.
Resizing works fine.
And I also try to implement resizing after rotation. Here's my approach with a humble illustration:
Grab the coordinates of the red point (it's mouse cursor coordiantes)
Derotate the point using negative angle to get derotated coordinates
function rotatePoint(x, y, center_x, center_y, angle) {
var new_x = (x - cx) * Math.cos(angle) - (y - cy) * Math.sin(angle) + cx;
var new_y = (x - cx) * Math.sin(angle) + (y - cy) * Math.cos(angle) + cy;
return [new_x, new_y]
}
Update xTopLeft, yTopLeft and redraw
Done
The idea behind this approach is simple. Since my drawing function depeneds on top left rectangle point and bottom right rectangle point I just need to get their new coordinates.
For instance, here's a simplified code for B point:
if (point == 'B') {
var newPointB = rotatePoint(mouse.x, mouse.y, center_x, center_y, -r);
xBottomRight = newPointB[0];
yTopLeft = newPointB[1];
}
But it doesn't work as expected: while resizing my rotated rectangle shifts, jumps and totally misbehaves.
In search of insights I've stumbled upon this article. The article covers my problem, but I don't get author's approach and can't implement it.
Why should I always lock the coordinates of the A point? My top left handle is intended to resize the rectangle in a north-west direction, so it would be necessary to change the coordinates of the A point...
Why should we recalculate the center point before derotation? It breaks the idea of uniform matrix transformations...
What's the correct algorithm in my case?
I was also facing same problem. It turned out that the angle I was using was in degree. Try multiplying angle in rotatePoint function with (Math.PI / 180).
I have a bubble field with elliptical bubbles. I'd like the bubbles to stretch -- elongate -- as they move to the edges of the canvas. This works on the sides, but top & bottom bubbles are getting flattened. I was trying to use atan2 to identify the angle and point that bubble's nose in that direction.
But when I add the rotation, all bubbles rotate. Is there a way to trap each bubble, calculate it's angle and rotate only that bubble instead of the whole context? ctx.rotation rotates the whole canvas. Makes sense. But this.rotation (currently commented out on line 149 of my codepen) has all bubbles spinning the same.
bubble.prototype.draw = function(){
ctx.beginPath();
ctx.save();
var p1 = { x: 0, y: 0 };
var p2 = { x: this.location.x, y: this.location.y };
this.rotation = Math.atan2(p2.y - p1.y, p2.x - p1.x) * 180 / Math.PI;
ctx.fillStyle = this.colour;
ctx.ellipse(this.location.x, this.location.y,
this.radius, this.radiusY,
this.rotation,
0,360, 0);
ctx.closePath();
ctx.fill();
ctx.restore();
}
Here's the whole codepen
Or maybe there's another way to radiate out shapes according to their radiant?
I moved the rotation calculation out of thebubble.prototype.draw and into the bubble function. Then, I took the velocity instead of the location and everything lined up!
var p2 = { x: this.velocity.x, y: this.velocity.y };
Codepen updated!
I am looking for a way to draw multiple rectangles around a circle at an angle facing the centre. What i have so far, currently just drawing the rectangles around a circle facing one direction lacking the angle inclination towards the centre -
https://thysultan.com/projects/thyplayer/
What i want is for the rectangles to incline at an angle such that each rectangle is facing the centre of the circle at it's designated position.
How would one do that?
Trigonometry approach
To animate the lines towards the center you can use either transforms or trigonometry math. Personally I find it easier with math in cases like this so here is such an example.
See #markE's answer for an example on how to use transforms (transforms can be easier on the eye and in the code in general).
Some prerequisites:
We know canvas is oriented so that a 0° angle points towards right. This is essential if you want to mark the frequency range somehow.
We need to calculate the length of the line from outer radius towards center (inner radius)
We need to calculate the end points of the line based on the angle.
An easy solution can be to make a function that calculates a single line at a center (cx, cy) at a certain angle (in radians), with t as length, t being in the range [0, 1] (as the FFT floating point buffer bins). We also provide an outer and inner radius to enable limiting the line:
function getLine(cx, cy, angle, t, oRadius, iRadius) {
var radiusDiff = oRadius - iRadius, // calc radius diff to get max length
length = radiusDiff * t; // now we have the line length
return {
x1: oRadius * Math.cos(angle), // x1 point (outer)
y1: oRadius * Math.sin(angle), // y1 point (outer)
x2: (oRadius - length) * Math.cos(angle), // x2 point (inner)
y2: (oRadius - length) * Math.sin(angle) // y2 point (inner)
}
}
All we need to do now is to feed it data from the FFT analyzer.
NB: Since all lines points towards center, you will have a crowded center area. Something to have in mind when determine the line widths and inner radius as well as number of bins to use.
Example demo
For the example I will just use some random data for the "FFT" and plot 64 bins.
// angle - in radians
function getLine(cx, cy, angle, t, oRadius, iRadius) {
var radiusDiff = oRadius - iRadius, // calc radius diff to get max length
length = radiusDiff * t; // now we have the line length
return {
x1: cx + oRadius * Math.cos(angle), // x1 point (outer)
y1: cy + oRadius * Math.sin(angle), // y1 point (outer)
x2: cx + (oRadius - length) * Math.cos(angle), // x2 point (inner)
y2: cy + (oRadius - length) * Math.sin(angle) // y2 point (inner)
}
}
// calculate number of steps based on bins
var ctx = document.querySelector("canvas").getContext("2d"),
fftLength = 64,
angleStep = Math.PI * 2 / fftLength,
angle = 0,
line;
ctx.beginPath(); // not needed in demo, but when animated
while(angle < Math.PI*2) {
// our line function in action:
line = getLine(250, 250, angle, getFFT(), 240, 50);
ctx.moveTo(line.x1, line.y1); // add line to path
ctx.lineTo(line.x2, line.y2);
angle += angleStep // get next angle
}
ctx.lineWidth = 5; // beware of center area
ctx.stroke(); // stroke all lines at once
// to smooth the "FFT" random data
function getFFT() {return Math.random() * 0.16 + 0.4}
<canvas width=500 height=500></canvas>
Here is an example!
I am trying to reset the green arc inside drawValueArc() so that each time you click the change button, the green arc is removed and redrawn. How can I remove it without removing the entire canvas? Also, as an aside, I have noticed that Math.random() * 405 * Math.PI / 180 doesn't actually always result in an arc that fits inside the gray arc, sometimes it is larger than the gray arc, why is this?
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cx = 150;
var cy = 150;
var startRadians = 135 * Math.PI / 180;
var endRadians = 405 * Math.PI / 180;
//main arc
ctx.beginPath();
ctx.arc(cx, cy, 58, startRadians, endRadians, false);
ctx.strokeStyle="rgb(220,220,220)";
ctx.lineWidth = 38;
ctx.stroke();
$('#setRandomValue').click(function(){
drawValueArc(Math.random() * 405 * Math.PI / 180);
});
function drawValueArc(val){
//ctx.clearRect(0, 0, W, H);
ctx.beginPath();
ctx.arc(cx, cy, 58, startRadians, val, false);
ctx.strokeStyle = "green";
ctx.lineWidth = 38;
ctx.stroke();
}
Drawing past boundary
The problem you are facing is in first instance the fact you are drawing before and after a 0-degree on the circle. This can be complicated to handle as you need to split in two draws: one for the part up to 0 (360) and one 0 to the remaining part.
There is a simple trick you can use to make this easier to deal with and that is to deal with all angles from 0 and use an offset when you draw.
Demo using redraw base (I moved it to jsfiddle as jsbin did not work for me):
http://jsfiddle.net/3dGLR/
Demo using off-screen canvas
http://jsfiddle.net/AbdiasSoftware/Dg9Jj/
First, some optimizations and settings for the offset:
var startRadians = 0; //just deal with angles
var endRadians = 300;
var deg2rad = Math.PI / 180; //pre-calculate this to save some cpu cycles
var offset = 122; //adjust this to modify rotation
We will now let the main function, drawArc() do all calculations for us so we can focus on the numbers - here we also offset the values:
function drawArc(color, start, end) {
ctx.beginPath();
ctx.arc(cx, cy, 58,
(startRadians + offset) * deg2rad,
(end + offset) * deg2rad, false);
ctx.strokeStyle = color;
ctx.lineWidth = 38;
ctx.stroke();
}
Clearing the previous arc
There are several techniques to clear the previous drawn arc:
You can draw the base arc to an off-screen canvas and use drawImage() to erase the old.
You can do as in the following example, just re-draw it with the base color
As with 2. but subtracting the green arc and draw the base color from the end of the green arc to the end of the base arc.
clearing the whole canvas with fillRect or clearRect.
1 and 3 are the fastest, while 4 is the slowest.
With out re-factored function (drawArc) it's as easy as this:
function drawValueArc(val) {
drawArc("rgb(220,220,220)", startRadians, endRadians);
drawArc("green", startRadians, val);
}
As everything now is 0-based concerning start we really don't need to give any other argument than 0 to the drawArc instead of startRadians. Use the new offset to offset the start position and adjust the endRadians to where you want it to stop.
As you can see in the demo, using this technique keeps everything in check without the need to draw in split.
Tip: if you notice green artifacts on the edges: this is due to anti-alias. Simply reduce the line width for the green color by 2 pixels (see demo 2, off-screen canvas).
Recently I got a task that is to draw circles on my own website with Google Maps API.
The complexity is the center of the circle is representing a "signal transmitter" and I need to make the circle transparent, with the opacity of each pixel reprseting the signal intensity of the exact location.
My basic idea is to extend the "Overlay" of Google Map API, so I have to write it in javascript I think.
The key part is to draw a circle with gradually changing opacity (inner stronger, outter lighter) and idealy, I can specify the opacity of each pixel.
I've been looking for approaches like CSS3, SVG, VML and even jquery and AJAX but still having no idea about how to archve this.
Thank you very much for your helping!
It looks like you're going to have to manually set every pixel, if you want that level of control over the opacity. I'd use something like this:
var centerX = 100 // Center X coordinate of the circle.
var centerY = 100 // Center Y coordinate of the circle.
var radius = 25 // Radius of circle.
//(var diameter = 2 * radius // Diameter of circle)
for(x = -radius; x < radius; x++){
for(y = -radius; y < radius; y++){
var hypotenuse = Math.sqrt((x * x) + (y * y)); // Line from point (x,y) to the center of the circle.
if(hypotenuse < radius){ // If the point falls within the circle
var opacity = hypotenuse / radius; // Should return a value between 0 and 1
drawPointAt(x + centerX, y + centerY, colour, opacity); // You'll have to look up what function to use here, yourself.
}
}
}
Here's a small example of this code returning a circle.
Here I got the solution. It's making use of the Canvas element of HTML5 (which is widely supported).
Here is the javascript code for locating the canvas element and draw the circle with gradually changing transparency. The key part is to use the "Gradient".
//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");
//draw a circle
gradient = ctx.createRadialGradient(200, 200, 0, 200, 200, 200);
gradient.addColorStop("0", "blue");
gradient.addColorStop("1.0", "transparent");
ctx.fillStyle = gradient;
//ctx.beginPath();
ctx.arc(200, 200, 200, 0, Math.PI*2, true);
//ctx.closePath();
ctx.fill();