I am drawing oval using bezierCurveTo method of canvas. I have to highlight the points on boundary of oval(same as oval shape in powerpoint). I want the exact position of all eight coordinates to place them on canvas. Please refer to attached screenshot
I would recommend not to use a Bezier to create an oval/ellipse - it's mathematical inaccurate and will just cause headache when you want to use points such as in this case.
I would suggest to create your own ellipse function - it's easy; this creates an ellipse as a path which you can fill and stroke etc.:
function drawEllipse(cxt, cx, cy, rx, ry) {
ctx.beginPath();
ctx.moveTo(cx + rx, cy);
for(var a = 0, step = 0.02, max = Math.PI * 2; a < max; a += step)
ctx.lineTo(cx + rx * Math.cos(a), cy+ ry * Math.sin(a));
}
Now, to get those edge points all you need to do is have a similar function (or modify the previous) doing the same but with less granular steps as well as returning the calculated points - count is number of points you want. The resulting array here in this example will return points arranges as [x1, y1, x2, y2, ...] - this is something you can adjust as you need:
function getEllipsePoints(cxt, cx, cy, rx, ry, count) {
var points = [],
a = 0, max = Math.PI * 2,
step = max / count
for(; a < max; a += step)
points.push(cx + rx * Math.cos(a), cy+ ry * Math.sin(a));
return points;
}
Now you can plot the points at the edges as you want (and have a mathematical correct ellipse as well as hit points for mouse).
Live demo here
Result:
Related
I have the xy coordinates from before and during a drag event, this.x and this.y```` are the current coordinates,this.lastXandthis.lastY``` are the origin.
What I need to do is given a radian of the source element, determine which mouse coordinate to use, IE if the angle is 0 then the x coordinates is used to give a "distance" if the degrees are 90 then the y coordinates are used
if the radian is 0.785398 then both x and y would need to be used.
I have the following code for one axis, but this only flips the y coordinates
let leftPosition;
if (this.walls[this.dragItem.wall].angle < Math.PI / 2) {
leftPosition = Math.round((-(this.y - this.lastY) / this.scale + this.dragItem.origin.left));
} else {
leftPosition = Math.round(((this.y - this.lastY) / this.scale + this.dragItem.origin.left));
}
I have an example here https://engine.owuk.co.uk
what I need to do is have the radian dictate what x or y coordinate is used to control the drag of the item by calculating the leftPosition, I have been loosing my mind trying to get this to work :(
The Math.sin and Math.cos is what you need, here is an example
<canvas id="c" width=300 height=150></canvas>
<script>
const ctx = document.getElementById('c').getContext('2d');
function drawShape(size, angle, numPoints, color) {
ctx.beginPath();
for (j = 0; j < numPoints; j++) {
a = angle * Math.PI / 180
x = size * Math.sin(a)
y = size * Math.cos(a)
ctx.lineTo(x, y);
angle += 360 / numPoints
}
ctx.fillStyle = color;
ctx.fill();
}
ctx.translate(80, 80);
drawShape(55, 0, 7, "green");
drawShape(45, 0, 5, "red");
drawShape(35, 0, 3, "blue");
ctx.translate(160, 0);
drawShape(55, 15, 7, "green");
drawShape(45, 35, 5, "red");
drawShape(35, 25, 3, "blue");
</script>
Here is a theoretical answer to your problem.
In the simplest way, you have an object within a segment that has to move relative to the position of the mouse, but constrained by the segment's vector.
Here is a visual representation:
So with the mouse at the red arrow, the blue circle needs to move to the light blue.
(the shortest distance between a line and a point)
How do we do that?
Let's add everything we can to that image:
The segment and the mouse form a triangle and we can calculate the length of all sides of that triangle.
The distance between two points is an easy Pythagorean calculation:
https://ncalculators.com/geometry/length-between-two-points-calculator.htm
Then we need the height of the triangle where the base is our segment:
https://tutors.com/math-tutors/geometry-help/how-to-find-the-height-of-a-triangle
That will give us the distance from our mouse to the segment, and we do know the angle by adding the angle of the segment + 90 degrees (or PI/2 in radians) that is all that we need to calculate the position of our light blue circle.
Of course, we will need to also add some min/max math to not exceed the boundaries of the segment, but if you made it this far that should be easy pickings.
I was able to make the solution to my issue
let position;
const sin = Math.sin(this.walls[this.dragItem.wall].angle);
const cos = Math.cos(this.walls[this.dragItem.wall].angle);
position = Math.round(((this.x - this.lastX) / this.scale * cos + (this.y - this.lastY) / this.scale * sin) + this.dragItem.origin.left);
I'm trying to highlight pixels that fall within a sector of a circle. I'm writing a shader to do this, but I'm implementing the logic in JavaScript until I get it right.
Essentially, each pixel coordinate in a canvas is scaled to be between 0 and 1, and is passed into the following code along with the canvas context:
function isWithinSector(ctx, x, y) {
let startAngle = degToRad(135), endAngle = degToRad(205);
// Distance of pixel from the circle origin (0.5, 0.5).
let dx = scaledX - 0.5;
let dy = scaledY - 0.5;
let angle = Math.atan2(dy, dx);
if (angle >= startAngle && angle <= endAngle) {
ctx.fillStyle = "rgba(255, 255, 0, .5)";
ctx.fillRect(x, y, 1, 1);
}
}
This works fine for some angles, but not for others. Pixels highlighted between 135 and 205 degrees appear like this (i.e. only 135 to 180 degrees are highlighted):
Note that the highlighted pixels don't match my black arc (the source of truth). I've been trying all kinds of things from Google but I'm stuck.
I have a CodePen that shows the issue: https://codepen.io/chrisparton1991/pen/XRpqXb. Can anybody guide me on what I'm doing wrong in my algorithm?
Thanks!
You get the problem if the angle is greater than 180°, as the atan2 function will then return a negative angle that is 360° smaller. This can be corrected by
let angle = Math.atan2(dy, dx);
if (angle<0) angle += 2*Math.PI;
But this is still not sufficient if you want to highlight the sector from 350° to 10°, that is, the small sector containing the 0° ray. Then the following extended normalization procedure helps.
let angle = Math.atan2(dy, dx);
let before = angle-startAngle;
if(before < -Math.PI)
before += 2*Math.PI;
let after = angle-endAngle;
if(after < -Math.PI)
after += 2*Math.PI;
Note that your image is upside-down as the screen origin is top-right, where you put the coordinates (0,1).
I've been recently adding shadows to a project. I've ended up with something that I like, but the shadows are a solid transparent color throughout. I would prefer them to be a fading gradient as they go further.
What I currently have:
What I'd like to achieve:
Right now I'm using paths to draw my shadows on a 2D Canvas. The code that is currently in place is the following:
// Check if edge is invisible from the perspective of origin
var a = points[points.length - 1];
for (var i = 0; i < points.length; ++i, a = b)
{
var b = points[i];
var originToA = _vec2(origin, a);
var normalAtoB = _normal(a, b);
var normalDotOriginToA = _dot(normalAtoB, originToA);
// If the edge is invisible from the perspective of origin it casts
// a shadow.
if (normalDotOriginToA < 0)
{
// dot(a, b) == cos(phi) * |a| * |b|
// thus, dot(a, b) < 0 => cos(phi) < 0 => 90° < phi < 270°
var originToB = _vec2(origin, b);
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(a.x + scale * originToA.x,
a.y + scale * originToA.y);
ctx.lineTo(b.x + scale * originToB.x,
b.y + scale * originToB.y);
ctx.lineTo(b.x, b.y);
ctx.closePath();
ctx.globalAlpha = _shadowIntensity / 2;
ctx.fillStyle = 'black';
ctx.fillRect(_innerX, _innerY, _innerWidth, _innerHeight);
ctx.globalAlpha = _shadowIntensity;
ctx.fill();
ctx.globalAlpha = 1;
}
}
Suggestions on how I could go about achieving this? Any and all help is highly appreciated.
You can use composition + the new filter property on the context which takes CSS filters, in this case blur.
You will have to do it in several steps - normally this falls under the 3D domain, but we can "fake" it in 2D as well by rendering a shadow-map.
Here we render a circle shape along a line represented by length and angle, number of iterations, where each iteration increasing the blur radius. The strength of the shadow is defined by its color and opacity.
If the filter property is not available in the browser it can be replaced by a manual blur (there are many out there such as StackBoxBlur and my own rtblur), or simply use a radial gradient.
For multiple use and speed increase, "cache" or render to an off-screen canvas and when done composite back to the main canvas. This will require you to calculate the size based on max blur radius as well as initial radius, then render it centered at angle 0°. To draw use drawImage() with a local transform transformed based on start of shadow, then rotate and scale (not shown below as being a bit too broad).
In the example below it is assumed that the main object is drawn on top after the shadow has been rendered.
The main function takes the following arguments:
renderShadow(ctx, x, y, radius, angle, length, blur, iterations)
// ctx - context to use
// x/y - start of shadow
// radius - shadow radius (assuming circle shaped)
// angle - angle in radians. 0° = right
// length - core-length in pixels (radius/blur adds to real length)
// blur - blur radius in pixels. End blur is radius * iterations
// iterations - line "resolution"/quality, also affects total end blur
Play around with shape, shadow color, blur radius etc. to find the optimal result for your scene.
Demo
Result if browser supports filter:
var ctx = c.getContext("2d");
// render shadow
renderShadow(ctx, 30, 30, 30, Math.PI*0.25, 300, 2.5, 20);
// show main shape
ctx.beginPath();
ctx.moveTo(60, 30);
ctx.arc(30, 30, 30, 0, 6.28);
ctx.fillStyle = "rgb(0,140,200)";
ctx.fill();
function renderShadow(ctx, x, y, radius, angle, length, blur, iterations) {
var step = length / iterations, // calc number of steps
stepX = step * Math.cos(angle), // calc angle step for x based on steps
stepY = step * Math.sin(angle); // calc angle step for y based on steps
for(var i = iterations; i > 0; i--) { // run number of iterations
ctx.beginPath(); // create some shape, here circle
ctx.moveTo(x + radius + i * stepX, y + i * stepY); // move to x/y based on step*ite.
ctx.arc(x + i * stepX, y + i * stepY, radius, 0, 6.28);
ctx.filter = "blur(" + (blur * i) + "px)"; // set filter property
ctx.fillStyle = "rgba(0,0,0,0.5)"; // shadow color
ctx.fill();
}
ctx.filter = "none"; // reset filter
}
<canvas id=c width=450 height=350></canvas>
I need to take a long (max resolution) image and wrap it into a circle. So imagine bending a steel bar so that it is now circular with each end touching.
I have been banging my head against threejs for the last 8 hours and have so far managed to apply the image as a texture on a circle geometry, but can't figure out how to apply the texture to a long mesh and then warp that mesh appropriately. The warping doesn't need to be (and shouldn't be) animated. What we basically have is a 360 panoramic image that we need to "flatten" into a top-down view.
In lieu of sharing my code (as it's not significantly different), I've so far been playing around with this tutorial:
http://www.johannes-raida.de/tutorials/three.js/tutorial06/tutorial06.htm
And I do (I think) understand the broad strokes at this point.
Other things I've tried is to use just canvas to slice the image up into strips and warp each strip... this was horribly slow and I couldn't get that to work properly either!
Any help/suggestions?
Here's also a shader version: Shadertoy - Circle Distortion
This is the actual code:
#define dPI 6.28318530718 // 2*PI
#define sR 0.3 // small radius
#define bR 1.0 // big radius
void main(void)
{
// calc coordinates on the canvas
vec2 uv = gl_FragCoord.xy / iResolution.xy*2.-vec2(1.);
uv.x *= iResolution.x/iResolution.y;
// calc if it's in the ring area
float k = 0.0;
float d = length(uv);
if(d>sR && d<bR)
k = 1.0;
// calc the texture UV
// y coord is easy, but x is tricky, and certain calcs produce artifacts
vec2 tUV = vec2(0.0,0.0);
// 1st version (with artifact)
//tUV.x = atan(uv.y,uv.x)/dPI;
// 2nd version (more readable version of the 3rd version)
//float disp = 0.0;
//if(uv.x<0.0) disp = 0.5;
//tUV.x = atan(uv.y/uv.x)/dPI+disp;
// 3rd version (no branching, ugly)
tUV.x = atan(uv.y/uv.x)/dPI+0.5*(1.-clamp(uv.x,0.0,1.0)/uv.x);
tUV.y = (d-sR)/(bR-sR);
// output pixel
vec3 col = texture2D(iChannel0, tUV).rgb;
gl_FragColor = vec4(col*k,1.);
}
So you could draw rectangle on the canvas and add this shader code.
I hope this helps.
So here's a function using canvas's context2d that does the job.
The idea is to go around all the circle by a small angular step and to draw a thin slice of 'texture' along the circle radius.
To make it faster, only way i see is to compute by hand the transform to do one single setTransform instead of all this stuff.
The step count is optimal with step = atan(1, radius)
(if you do the scheme it's obvious : to go one y up when you're radius far from the center then tan = 1/radius => step angle = atan(1, radius).)
fiddle is here :
http://jsfiddle.net/gamealchemist/hto1s6fy/
A small example with a cloudy landscape :
// draw the part of img defined by the rect (startX, startY, endX, endY) inside
// the circle of center (cx,cy) between radius (innerRadius -> outerRadius)
// - no check performed -
function drawRectInCircle(img, cx, cy, innerRadius, outerRadius, startX, startY, endX, endY) {
var angle = 0;
var step = 1 * Math.atan2(1, outerRadius);
var limit = 2 * Math.PI;
ctx.save();
ctx.translate(cx, cy);
while (angle < limit) {
ctx.save();
ctx.rotate(angle);
ctx.translate(innerRadius, 0);
ctx.rotate(-Math.PI / 2);
var ratio = angle / limit;
var x = startX + ratio * (endX - startX);
ctx.drawImage(img, x, startY, 1, (endY - startY), 0, 0, 1, (outerRadius - innerRadius));
ctx.restore();
angle += step;
}
ctx.restore();
}
How can I detect when the user clicks inside the red bubble?
It should not be like a square field. The mouse must be really inside the circle:
Here's the code:
<canvas id="canvas" width="1000" height="500"></canvas>
<script>
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d")
var w = canvas.width
var h = canvas.height
var bubble = {
x: w / 2,
y: h / 2,
r: 30,
}
window.onmousedown = function(e) {
x = e.pageX - canvas.getBoundingClientRect().left
y = e.pageY - canvas.getBoundingClientRect().top
if (MOUSE IS INSIDE BUBBLE) {
alert("HELLO!")
}
}
ctx.beginPath()
ctx.fillStyle = "red"
ctx.arc(bubble.x, bubble.y, bubble.r, 0, Math.PI*2, false)
ctx.fill()
ctx.closePath()
</script>
A circle, is the geometric position of all the points whose distance from a central point is equal to some number "R".
You want to find the points whose distance is less than or equal to that "R", our radius.
The distance equation in 2d euclidean space is d(p1,p2) = root((p1.x-p2.x)^2 + (p1.y-p2.y)^2).
Check if the distance between your p and the center of the circle is less than the radius.
Let's say I have a circle with radius r and center at position (x0,y0) and a point (x1,y1) and I want to check if that point is in the circle or not.
I'd need to check if d((x0,y0),(x1,y1)) < r which translates to:
Math.sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)) < r
In JavaScript.
Now you know all these values (x0,y0) being bubble.x and bubble.y and (x1,y1) being x and y.
To test if a point is within a circle, you want to determine if the distance between the given point and the center of the circle is smaller than the radius of the circle.
Instead of using the point-distance formula, which involves the use of a (slow) square root, you can compare the non-square-rooted (or still-squared) distance between the points. If that distance is less than the radius squared, then you're in!
// x,y is the point to test
// cx, cy is circle center, and radius is circle radius
function pointInCircle(x, y, cx, cy, radius) {
var distancesquared = (x - cx) * (x - cx) + (y - cy) * (y - cy);
return distancesquared <= radius * radius;
}
(Not using your code because I want to keep the function general for onlookers who come to this question later)
This is slightly more complicated to comprehend, but its also faster, and if you intend on ever checking point-in-circle in a drawing/animation/object moving loop, then you'll want to do it the fastest way possible.
Related JS perf test:
http://jsperf.com/no-square-root
Just calculate the distance between the mouse pointer and the center of your circle, then decide whether it's inside:
var dx = x - bubble.x,
dy = y - bubble.y,
dist = Math.sqrt(dx * dx + dy * dy);
if (dist < bubble.r) {
alert('hello');
}
Demo
As mentioned in the comments, to eliminate Math.sqrt() you can use:
var distsq = dx * dx + dy * dy,
rsq = bubble.r * bubble.r;
if (distsq < rsq) {
alert('HELLO');
}
An alternative (not always useful meaning it will only work for the last path (re)defined, but I bring it up as an option):
x = e.pageX - canvas.getBoundingClientRect().left
y = e.pageY - canvas.getBoundingClientRect().top
if (ctx.isPointInPath(x, y)) {
alert("HELLO!")
}
Path can btw. be any shape.
For more details:
http://www.w3.org/TR/2dcontext/#dom-context-2d-ispointinpath