I have created an ellipse on my canvas and now I need to draw three lines stemming from the origin. As an example let's say the first line is 90 degrees (vertical) so the point is (0, 10). I need the other two lines to be x pixels away from the point in both directions.
I'm sure I didn't describe this well enough but basically what I am trying to do is from a point on a known ellipse, find another point x distance away that lies on the ellipse.
I have tried looking for an arc of an ellipse but nothing seems to fit what I am looking for.
For an ellipse:
x = a cos(t)
y = b sin(t)
So:
x/a= cos(t)
t = acos(x/a)
y = b sin(acos(x/a))
Plug in your values of a, b, and x and you get y.
See https://www.mathopenref.com/coordparamellipse.html
Rather crudely:
var a=120;
var b=70;
var c=document.getElementById("myCanvas");
var cxt=c.getContext("2d");
var xCentre=c.width / 2;
var yCentre=c.height / 2;
// draw axes
cxt.strokeStyle='blue';
cxt.beginPath();
cxt.moveTo(0, yCentre);
cxt.lineTo(xCentre*2, yCentre);
cxt.stroke();
cxt.beginPath();
cxt.moveTo(xCentre, 0);
cxt.lineTo(xCentre, yCentre*2);
cxt.stroke();
// draw ellipse
cxt.strokeStyle='black';
cxt.beginPath();
for (var i = 0 * Math.PI; i < 2 * Math.PI; i += 0.01 ) {
xPos = xCentre - (a * Math.cos(i));
yPos = yCentre + (b * Math.sin(i));
if (i == 0) {
cxt.moveTo(xPos, yPos);
} else {
cxt.lineTo(xPos, yPos);
}
}
cxt.lineWidth = 2;
cxt.strokeStyle = "#232323";
cxt.stroke();
cxt.closePath();
// draw lines with x=+/- 40
var deltaX=40;
var y1=b*Math.sin(Math.acos(deltaX/a));
cxt.strokeStyle='red';
cxt.beginPath();
cxt.moveTo(xCentre+deltaX, yCentre-y1);
cxt.lineTo(xCentre, yCentre);
cxt.lineTo(xCentre-deltaX, yCentre-y1);
cxt.stroke();
<html>
<head><title>Ellipse</title></head>
<body>
<canvas id="myCanvas" style="position: absolute;" width="400" height="200"></canvas>
</body>
</html>
(Using https://www.scienceprimer.com/draw-oval-html5-canvas as a basis as I've never used HTML canvas before.)
Andrew Morton's answer is adequate, but you can it with one square root instead of a sin and an acos.
Suppose you have an ellipse centered at the origin, with a radius along the X-axis of a and a radius along the Y-axis of b. The equation of this ellipse is
x2/a2 + y2/b2 = 1.
Solving this for y gives
y = ± b sqrt(1 - x2/a2)
You can choose whichever sign is appropriate. Based on your post, you want the positive square root.
Translating to Javascript:
function yForEllipse(a, b, x) {
return b * Math.sqrt(1 - x*x / a * a);
}
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 make a simple shape animate along a square path based on a set 'radius'. Atm I'm using a sine wave to set the position over time, so its basically animating along a circular path.
Is there a way using maths to alter the sine wave to make the animation square. I know there are other ways to do this, but I'd be interested to learn the math behind it.
I have an example fiddle:
t = new Date().getTime()
r = 25
x = (r * Math.cos t * 0.005)
y = (r * Math.sin t * 0.005)
http://jsfiddle.net/Z5hrM/1/
We can do better than just circle or square! The equations for x and y can be generalized with an exponent D:
x = (r^D * cos(theta))^(1/D) and y = (r^D * sin(theta))^(1/D)
When D = 1 you have the familiar equations that give a circle. When D = 0.5 you get a diamond, when D < 0.5 you get pointed stars. When D > 1 you get increasingly blocky shapes, and as D -> infinity you get a square.
Give it a try with this snippet; you can type new values of D as the animation proceeds.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>animation problem</title>
<script type='text/javascript'>
function demo(){
var w = 400;
var ctx = document.getElementById("canvas").getContext("2d");
ctx.canvas.width = w;
ctx.canvas.height = w;
var r = w/4;
var theta = 0;
setInterval(function(){
ctx.canvas.width += 0; // clear the canvas
ctx.translate(w/2, w/2); // center it on (0,0)
var D = +document.getElementById("exponent").value;
var xSign = Math.cos(theta) < 0 ? -1 : 1; // Handle all quadrants this way
var ySign = Math.sin(theta) < 0 ? -1 : 1;
var x = xSign*Math.pow( Math.pow(r, D)*Math.abs(Math.cos(theta)), 1/D );
var y = ySign*Math.pow( Math.pow(r, D)*Math.abs(Math.sin(theta)), 1/D );
ctx.fillStyle = "blue";
ctx.arc( x, y, 20, 0, 6.2832, false );
ctx.fill();
theta += Math.PI/100;
}, 20);
}
</script>
</head>
<body onload='demo()'>
<input id='exponent' type=text value='1'\>
<br />
<canvas id='canvas'></canvas>
</body>
</html>
jsFiddle Demo
It actually isn't going to take much modification. Considering that the cosine represents the x coordinate, and the sin represents the y coordinate, it should be obvious that to make a square path one of these values must be set to a whole value instead of a partial value.
As a result, Math.cos t and Math.sin t will need to be regulated with a variable and a condition
xcos = Math.cos t * 0.005
ysin = Math.sin t * 0.005
if Math.abs(xcos) > Math.abs(ysin)
xcos = Math.round(xcos)
else
ysin = Math.round(ysin)
x = #cx + (radius * xcos)
y = #cy + (radius * ysin)
Your variable r should be a vector of two position (x,y) that will handle the position/increment on x and y respectively. See when you do this x = (0 * Math.cos t * 0.005) the circule just get moved from up to down. In order to get a shape behavior you need to control the vector (x and y positions) over the time and use remainder to wrap up x and y position (%).
Regards.
Given an angle between 0° and 90°, generate a SVG gradient that fills entire rectangle.
SVG gradients accept two control points rather than angle. Here is the code of the first square on the picture above:
<linearGradient x1="0" y1="0" x2="1" y2="0.5">
The problem is that the gradient doesn’t cover the entire square. I want to extend the gradient just enough to fill the shape entirely so the red triangle would be not visible. Here is an interactive demo (tested in Chrome, Firefox and Safari) to give you a better idea.
Solution in JavaScript:
function angleToVector(angle) {
var od = Math.sqrt(2);
var op = Math.cos(Math.abs(Math.PI/4 - angle)) * od;
var x = op * Math.cos(angle);
var y = op * Math.sin(angle);
return {x: x, y: y};
}
For angle between -180° and 180°:
function angleToPoints(angle) {
var segment = Math.floor(angle / Math.PI * 2) + 2;
var diagonal = (1/2 * segment + 1/4) * Math.PI;
var op = Math.cos(Math.abs(diagonal - angle)) * Math.sqrt(2);
var x = op * Math.cos(angle);
var y = op * Math.sin(angle);
return {
x1: x < 0 ? 1 : 0,
y1: y < 0 ? 1 : 0,
x2: x >= 0 ? x : x + 1,
y2: y >= 0 ? y : y + 1
};
}
There might be a simpler solution for this.
So your question as I understand it is this: given a rectangle (whose top left corner is the origin O = (0, 0) and whose bottom right corner is D = (w, h)) and a line l through point O at angle a (with 0° <= a <= 90°), find the point P = (x2, y2) on l such that line DP makes a right angle with l.
If you draw the diagonal of the rectangle, OD, it completes a right triangle with the right angle at P. The angle of that diagonal is atan(h/w), and if you take the absolute difference of that from a (i.e. |atan(h/w) - a|), you'll get the angle in that right triangle at point O. Then you can take the cosine of that angle to get the distance between O and P along l as a proportion of the length of OD (the hypotenuse). You can multiply out the hypotenuse, and then just multiply that by cos(a) and sin(a) to get x2 and y2, respectively.
To summarize:
|OD| = sqrt(w*w + h*h)
|OP| = cos(|atan(h/w) - a|) * |OD|
x2 = |OP| * cos(a)
y2 = |OP| * sin(a)
I'm trying to make half of a rectangle - devided diagonally - to fit inside a triangle.
Rotation works well, so does sizing of the rectangle. But once I try to skew it, it all gets messed up. Basically I want to simulate a 3D surface.
That means I have to find the angle of abc, where b is the center point. And then apply this angle as a skew to the rectangle. But for some reason that doesn't work as intended.
Here is a simple illustration of what I want to accomplish:
You will probably understand more once you take a look at the fiddle: http://jsfiddle.net/p7g7Y/11/
EDIT: Got the width right at least: http://jsfiddle.net/p7g7Y/12/
The piece of code you need to look at is at line 63 - 95.
Try comment out the transform, and you will see that rotation and size works well.
function triangle(a, b, c){
context.save();
//Draw the triangle
context.beginPath();
context.moveTo(a[0], a[1]);
context.lineTo(b[0], b[1]);
context.lineTo(c[0], c[1]);
context.lineTo(a[0], a[1]);
context.closePath();
context.stroke();
//Lets find the distance between a and b to set height of the image
var imgHeight = lineDistance(a, b);
//And the width b to c
var imgWidth = lineDistance(b, c);
//Now we gotta skew it acording to the rad between ba and bc
var skewAngle = find_angle(a,c,b); //Find angle and make it rad
//Find the angle of b to a line
var theta = Math.atan2(a[1] - b[1], a[0] - b[0]);
context.translate(a[0], a[1]); //Set origin of rotation
context.rotate(theta + 1.57079633); //Had to rotate it some more 1.57079633 = 90deg
context.transform(1, skewAngle, 0, 1, 0, 0);
context.rect( 0, 0, imgHeight, imgWidth);
context.stroke();
context.restore();
}
If anything is unclear, please ask! I would love some help on this!
It's easier if you solve the problem more generally: find a, b, c, d, e and f so that
// (x0, y0) maps to (x_0, y_0)
a*x0 + b*y0 + c = x_0
d*x0 + e*y0 + f = y_0
// (x1, y1) maps to (x_1, y_1)
a*x1 + b*y1 + c = x_1
d*x1 + e*y1 + f = y_1
// (x2, y2) maps to (x_2, y_2)
a*x2 + b*y2 + c = x_2
d*x2 + e*y2 + f = y_2
This 6x6 linear system is composed of two independent 3x3 linear systems:
a*x0 + b*y0 + c = x_0
a*x1 + b*y1 + c = x_1
a*x2 + b*y2 + c = x_2
d*x0 + e*y0 + f = y_0
d*x1 + e*y1 + f = y_1
d*x2 + e*y2 + f = y_2
Solving them gives you the 6 numbers to pass to setTransform to map any three points to other three points.
delta = x0*y1 + y0*x2 + x1*y2 - y1*x2 - y0*x1 - x0*y2
delta_a = x_0*y1 + y0*x_2 + x_1*y2 - y1*x_2 - y0*x_1 - x_0*y2
delta_b = x0*x_1 + x_0*x2 + x1*x_2 - x_1*x2 - x_0*x1 - x0*x_2
delta_c = x0*y1*x_2 + y0*x_1*x2 + x_0*x1*y2 - x_0*y1*x2 - y0*x1*x_2 - x0*x_1*y2
delta_d = y_0*y1 + y0*y_2 + y_1*y2 - y1*y_2 - y0*y_1 - y_0*y2
delta_e = x0*y_1 + y_0*x2 + x1*y_2 - y_1*x2 - y_0*x1 - x0*y_2
delta_f = x0*y1*y_2 + y0*y_1*x2 + y_0*x1*y2 - y_0*y1*x2 - y0*x1*y_2 - x0*y_1*y2
a = delta_a / delta
b = delta_b / delta
c = delta_c / delta
d = delta_d / delta
e = delta_e / delta
f = delta_f / delta
For a full description of 3d texture mapping using 2d canvas context see this more detailed answer.
Here’s how to calculate transforms necessary to fit a rectangle to a triangle:
Translate to the “pivot point” of your triangle – point B.
Rotate by the angle of side BC.
Skew in the X direction by the angle of corner B.
So, first translate:
// transform translate = pt2
var translate = pt2;
Then rotate:
// transform rotation = angleBC (based on slope of BC)
var rotation = Math.atan2((pt3.y-pt2.y),(pt3.x-pt2.x));
Finally skewX:
// transform skewX, based on angleB
var skewX = Math.tan(angleB-Math.PI/2);
Here’s how to get angleB for use in skewX:
// calculate segment lengths
var AB = Math.sqrt(Math.pow(pt2.x-pt1.x,2)+ Math.pow(pt2.y-pt1.y,2));
var BC = Math.sqrt(Math.pow(pt2.x-pt3.x,2)+ Math.pow(pt2.y-pt3.y,2));
var AC = Math.sqrt(Math.pow(pt3.x-pt1.x,2)+ Math.pow(pt3.y-pt1.y,2));
// calculate angleB using law of cosines
var angleB = Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB));
You’ll also need the width and height of the rectangle to draw:
// rectangle height = triangle altitude
var rectHeight = AB * Math.sin(angleB);
// rectangle width = triangle BC
var rectWidth = BC;
A small “gotcha”:
Your translate point is B, but rectangles are drawn starting at top-left.
This means you must offset your rectangle vertically by the rectHeight:
ctx.rect(0, -rectHeight, rectWidth, rectHeight);
Also, not really a “gotcha”, but more of a natual limitation:
The angle at corner B must be <180.
So, if your triangle “inverts”, I you’ll have to compensate by flipping points A and C.
Interesting project you have there!
Would you share a bit when you’re done?
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/KKELu/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var pt1={x:100,y:100};
var pt2={x:150,y:225};
var pt3={x:250,y:150};
drawTriangle();
drawRectangle();
function drawRectangle(){
// calc transform info
var info=analyzeTriangle();
ctx.save();
ctx.translate(info.translate.x,info.translate.y);
ctx.rotate(info.rotation);
ctx.transform(1,0,info.skewX,1,0,0);
ctx.beginPath();
// since rects origin is top left, must offset y by -height
ctx.rect(0,-info.rectHeight,info.rectWidth,info.rectHeight);
ctx.strokeStyle="purple";
ctx.stroke();
ctx.restore();
}
function drawTriangle(){
ctx.beginPath();
ctx.strokeStyle="blue";
ctx.moveTo(pt1.x,pt1.y);
ctx.lineTo(pt2.x,pt2.y);
ctx.lineTo(pt3.x,pt3.y);
ctx.closePath();
ctx.stroke();
ctx.fillStyle="rgba(255,255,0,0.10)";
ctx.fill();
}
function analyzeTriangle(){
// segment lengths
var AB = Math.sqrt(Math.pow(pt2.x-pt1.x,2)+ Math.pow(pt2.y-pt1.y,2));
var BC = Math.sqrt(Math.pow(pt2.x-pt3.x,2)+ Math.pow(pt2.y-pt3.y,2));
var AC = Math.sqrt(Math.pow(pt3.x-pt1.x,2)+ Math.pow(pt3.y-pt1.y,2));
// angleB = using law of cosines
var angleB = Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB));
// transform translate = pt2
var translate = pt2;
// transform rotation = angleBC (based on slope of BC)
var rotation = Math.atan2((pt3.y-pt2.y),(pt3.x-pt2.x));
// transform skewX, based on angleB
var skewX = Math.tan(angleB-Math.PI/2);
// rectangle height = triangle altitude
var rectHeight = AB * Math.sin(angleB);
// rectangle width = triangle BC
var rectWidth = BC;
return({
translate:translate,
rotation:rotation,
skewX:skewX,
rectHeight:rectHeight,
rectWidth:rectWidth
});
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=350 height=350></canvas>
</body>
</html>
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