Move an object along a circle in fabricjs - javascript

currently I'm using fabricjs to draw on a canvas.
I have one large half-circle and a small circle. I want that the user is able to move the small circle on the outer line of the large half circle. But how can I prevent that the circle leave it's path? The Math is not a problem ;)
EDIT: Here I have some code, that shows you, what I want.
deltaLeft = p.left - centerPointX;
deltaTop = p.top - centerPointY;
length = Math.sqrt(deltaLeft * deltaLeft + deltaTop * deltaTop);
console.log(length);
if((length <= centerRadius + 5) && (length >= centerRadius - 5) ){
handleNewX = p.left;
handleNewY = p.top;
}else{
p.left = handleNewX;
p.top = handleNewY;
}
https://jsfiddle.net/g1h2gL88/
The Problem is, it does not feel naturally to move the handle

The problem is the math, if you want to restrict the moment of the of the object on the circle it would calculate the angle of the vector from center to the mouse position. And then your calculate the new position using the angle and the centerRadius. You basically put a straight infinite line starting at the center trough mouse position, and then calculate the intersection of the line with the circle.
deltaLeft = p.left - centerPointX;
deltaTop = p.top - centerPointY;
var radians = Math.atan2(deltaTop, deltaLeft)
p.left = Math.cos(radians) * centerRadius + centerPointX
p.top = Math.sin(radians) * centerRadius + centerPointY
jsfiddle demo
If you really want to have a range centerRadius - 5 to centerRadius + 5 then this can be easily extended to:
var length = Math.sqrt(deltaLeft * deltaLeft + deltaTop * deltaTop);
// change the position only if mouse is outside if the centerRadius +/- 5 range
if (length <= centerRadius - 5 || length >= centerRadius + 5) {
var radians = Math.atan2(deltaTop, deltaLeft)
if (length < centerRadius) {
length = centerRadius - 5;
} else {
length = centerRadius + 5;
}
p.left = Math.cos(radians) * length + centerPointX
p.top = Math.sin(radians) * length + centerPointY
}
fiddle demo

Related

Drawing random shapes within a complex vector shape using InDeesign scripting and or basil.js

I am wondering if there is a simple way to draw a series of random ellipses (or other things) within a complex vector shape (say, a letterform) using basil.js or indesign scripting. I figured out how to place random sized and colored ellipses within a larger ellipse by using if statements and checking the distance of my random ellipses from the center fo the larger ellipse. But I can't for the life of me figure out a way to get my script to check and see if the random ellipses I am drawing fall within a more complex shape. Any help would be appreciated!
So this is what I have working as far as placing random ellipses within a larger ellipse:
And here is my code for that:
function stippleCircle(mR, rR, xP, yP, num){
for(i=0; i<num; i++){
noStroke();
println("stip " + i);
var eRad = mR; //maximum ellipse radius
var ranRad = rR //radius for small ellipses
var xPos = xP; //x position of large ellipse
var yPos = yP; //y position of large ellipse
var eRad = 0;
var xRan = random(xPos - eRad, xPos + eRad);
var yRan = random(yPos - eRad, yPos + eRad);
if(xRan <= xPos && yRan <= yPos){
if((xPos-xRan) * (xPos-xRan) + (yRan-yPos) * (yRan-yPos) <= eRad * eRad){
fill(100, 0, 0, 0); //C
var myEllipse = ellipse(xRan, yRan, ranRad, ranRad);
}
}else if(xRan > xPos && yRan <= yPos){
if((xRan-xPos) * (xRan-xPos) + (yRan-yPos) * (yRan-yPos) <= eRad * eRad){
fill(0, 100, 0, 0); //M
var myEllipse = ellipse(xRan, yRan, ranRad, ranRad);
}
}else if(xRan > xPos && yRan > yPos){
if((xRan-xPos) * (xRan-xPos) + (yPos-yRan) * (yPos-yRan) <= eRad * eRad){
fill(0, 0, 100, 0); //Y
var myEllipse = ellipse(xRan, yRan, ranRad, ranRad);
}
}else if(xRan <= xPos && yRan > yPos){
if((xPos-xRan) * (xPos-xRan) + (yPos-yRan) * (yPos-yRan) <= eRad * eRad){
fill(0, 0, 0, 100);
var myEllipse = ellipse(xRan, yRan, ranRad, ranRad);
}
}
blendMode(myEllipse, BlendMode.SOFT_LIGHT);
opacity(myEllipse, 70);
}
But how would I go about doing something similar with a more complex shape? The example below is illustrating this with the letter P and would likely use pathsToPoints() or something like that. Is this even possible? Maybe I should start with a vector shape that is simpler than a letter but more complex than an ellipse.

Detect mouse is near circle edge

I have a function which gets the mouse position in world space, then checks to see if the mouse is over or near to the circle's line.
The added complication how ever is the circle is transformed at an angle so it's more of an ellipse. I can't see to get the code to detect that the mouse is near the border of circle and am unsure where I am going wrong.
This is my code:
function check(evt){
var x = (evt.offsetX - element.width/2) + camera.x; // world space
var y = (evt.offsetY - element.height/2) + camera.y; // world space
var threshold = 20/scale; //margin to edge of circle
for(var i = 0; i < obj.length;i++){
// var mainAngle is related to the transform
var x1 = Math.pow((x - obj[i].originX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
var y1 = Math.pow((y - obj[i].originY),2) / Math.pow((obj[i].radius + threshold) * mainAngle,2);
var x0 = Math.pow((x - obj[i].originX),2) / Math.pow((obj[i].radius - threshold) * 1, 2);
var y0 = Math.pow((y - obj[i].originY),2) / Math.pow((obj[i].radius - threshold) * mainAngle, 2);
if(x1 + y1 <= 1 && x0 + y0 >= 1){
output.innerHTML += '<br/>Over';
return false;
}
}
output.innerHTML += '<br/>out';
}
To understand it better, I have a fiddle here: http://jsfiddle.net/nczbmbxm/ you can move the mouse over the circle, it should say "Over" when you are within the threshold of being near the circle's perimeter. Currently it does not seem to work. And I can't work out what the maths needs to be check for this.
There is a typo on line 34 with orignX
var x1 = Math.pow((x - obj[i].orignX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
should be
var x1 = Math.pow((x - obj[i].originX), 2) / Math.pow((obj[i].radius + threshold) * 1,2);
now you're good to go!
EDIT: In regards to the scaling of the image and further rotation of the circle, I would set up variables for rotation about the x-axis and y-axis, such as
var xAngle;
var yAngle;
then as an ellipse can be written in the form
x^2 / a^2 + y^2 / b^2 = 1
such as in Euclidean Geometry,
then the semi-major and semi-minor axes would be determined by the rotation angles. If radius is the circles actual radius. then
var semiMajor = radius * cos( xAngle );
var semiMinor = radius;
or
var semiMajor = radius;
var semiMinor = radius * cos( yAngle );
you would still need to do some more transformations if you wanted an x and y angle.
so if (xMouseC, yMouseC) are the mouse coordinates relative to the circles centre, all you must do is check if that point satisfies the equation of the ellipse to within a certain tolerance, i.e. plug in
a = semiMajor;
b = semiMinor;
x = xMouseC;
y = yMouseC;
and see if it is sufficiently close to 1.
Hope that helps!

How to prevent object from rotating 360deg when reaching full circle?

I have a image which always points in the direction of the mouse.
At some point the angle goes from 180deg to -180deg, how do I get the image to take the short angle instead of doing full circle?
// Find ship angle (Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;).
var mouseAngle = getAngle(FIREFLY.CENTER.X, FIREFLY.CENTER.Y, currentMousePos[0], currentMousePos[1]);
var turnDegrees = mouseAngle - FIREFLY.ANGLE;
var maxDegrees = 5;
console.log(mouseAngle + " " + FIREFLY.ANGLE);
if (turnDegrees > -5 && turnDegrees < 5) {
// Do nothing.
} else if (turnDegrees < 0) {
FIREFLY.ANGLE -= 5;
} else {
FIREFLY.ANGLE += 5;
}
// Set ship direction.
FIREFLY.style.transform = 'rotate(' + (FIREFLY.ANGLE + 90) + 'deg)';
Fiddle
Angles are the same modulo 360 degrees, so to avoid big rotations like 359° you choose -1° and to avoid -359° you choose 1°
You could do something like
var turnDegrees = (mouseAngle - FIREFLY.ANGLE) ;
if(-540<turnDegrees&&turnDegrees <=-180) turnDegrees+=360;
else if(180<turnDegrees&&turnDegrees<=540) turnDegrees-=360;
but it turns out FIREFLY.ANGLE can take large values, so to avoid plenty of other if clauses, a general formula is better. Here is one using modulo
var turnDegrees = mod(mouseAngle - FIREFLY.ANGLE +180, 360) -180;
function mod(x, value){ // Euclidean modulo
return x>=0 ? x%value : value+ x%value;
}
plot test
fiddle updated: http://jsfiddle.net/crl/2rz296tf/31
You could turn off CSS transforms, and do the interpolation yourself.
Try calculating the difference between the angles when the mouse moves and add that to a total angle, that way the angle wont be bound to (-180, 180)

Positioning image on Google Maps with rotate / scale / translate

I'm developing a user-interface for positioning an image on a google map.
I started from : http://overlay-tiler.googlecode.com/svn/trunk/upload.html which is pretty close to what I want.
But instead of 3 contact points I want a rotate tool, a scale tool and a translate tool (the later exists).
I tried to add a rotate tool but it doesn't work as I expected :
I put a dot on the left bottom corner that control the rotation (around the center of the image). The mouse drag the control dot and I calculate the 3 others points.
My code is based on the mover object but I changed the onMouseMove function :
overlaytiler.Rotater.prototype.rotateDot_ = function(dot, theta, origin) {
dot.x = ((dot.x - origin.x) * Math.cos(theta) - (dot.y - origin.y) * Math.sin(theta)) + origin.x;
dot.y = ((dot.x - origin.x) * Math.sin(theta) + (dot.y - origin.y) * Math.cos(theta)) + origin.y;
dot.render();
};
overlaytiler.Rotater.prototype.onMouseMove_ = function(e) {
var dots = this.controlDots_;
var center = overlaytiler.Rotater.prototype.getCenter_(dots);
// Diagonal length
var r = Math.sqrt(Math.pow(this.x - center.x, 2) + Math.pow(this.y - center.y, 2));
var old = {
x: this.x,
y: this.y
};
// Real position
var newPos = {
x: this.x + e.clientX - this.cx,
y: this.y + e.clientY - this.cy
}
var newR = Math.sqrt(Math.pow(newPos.x - center.x, 2) + Math.pow(newPos.y - center.y, 2));
var theta = - Math.acos((2 * r * r - (Math.pow(newPos.x - old.x, 2) + Math.pow(newPos.y - old.y, 2))) / (2 * r * r));
// Fixed distance position
this.x = (newPos.x - center.x) * (r / newR) + center.x;
this.y = (newPos.y - center.y) * (r / newR) + center.y;
dots[1].x = center.x + (center.x - this.x);
dots[1].y = center.y + (center.y - this.y);
dots[1].render();
overlaytiler.Rotater.prototype.rotateDot_(dots[2], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[0], theta, center);
// Render
this.render();
this.cx = e.clientX;
this.cy = e.clientY;
};
Unfortunately there is a problem with precision and angle sign.
http://jsbin.com/iQEbIzo/4/
After a few rotations the image is highly distorted and rotation is supported only in one direction.
I wonder how I can achieve a great precision and without any distortion.
Maybe my approach is useless here (try to move the corners at the right coordinates), I tried to rotate the image with the canvas but my attempts were unsuccessful.
Edit : Full working version : http://jsbin.com/iQEbIzo/7/
Here is my version of it. #efux and #Ben answers are far more complete and well designed however the maps don't scale in/out when you zoom in/out. Overlays very likely need to do this since they are used to put a "second map" or photograph over the existing map.
Here is the JSFiddle: http://jsfiddle.net/adelriosantiago/3tzzwmsx/4/
The code that does the drawing is the following:
DebugOverlay.prototype.draw = function() {
var overlayProjection = this.getProjection();
var sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
var ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
var div = this.div_;
div.style.left = sw.x + 'px';
div.style.top = ne.y + 'px';
div.style.width = (ne.x - sw.x) + 'px';
div.style.height = (sw.y - ne.y) + 'px';
div.style.transform = 'rotate(' + rot + 'deg)';
};
For sure this code could be implemented on efux and Ben code if needed but I haven't tried yet.
Note that the box marker does not updates its position when the rotation marker moves...
rotation is supported only in one direction
This is due to how you calculate the angle between two vectors.
It always gives you the same vector no matter if the mouse is right of the dot or not. I've found a solution in a german math board (unfortunately I cant access the site without using the cache of Google : cached version).
Note that in this example the angle α is on both sides the same and not as you would expect -α in the second one. To find out if the vector a is always on "the same side" of vector b you can use this formula.
ax*by - ay*bx
This is either positive or negative. You you simply can change the sign of the angle to α * -1.
I modified some parts of your code.
overlaytiler.Rotater.prototype.rotateDot_ = function(dot, theta, origin) {
// translate to origin
dot.x -= origin.x ;
dot.y -= origin.y ;
// perform rotation
newPos = {
x: dot.x*Math.cos(theta) - dot.y*Math.sin(theta),
y: dot.x*Math.sin(theta) + dot.y*Math.cos(theta)
} ;
dot.x = newPos.x ;
dot.y = newPos.y ;
// translate back to center
dot.x += origin.x ;
dot.y += origin.y ;
dot.render();
};
If you want to know, how I rotate the points please reference to this site and this one.
overlaytiler.Rotater.prototype.onMouseMove_ = function(e) {
var dots = this.controlDots_;
var center = overlaytiler.Rotater.prototype.getCenter_(dots);
// get the location of the canvas relative to the screen
var rect = new Array() ;
rect[0] = dots[0].canvas_.getBoundingClientRect() ;
rect[1] = dots[1].canvas_.getBoundingClientRect() ;
rect[2] = dots[2].canvas_.getBoundingClientRect() ;
// calculate the relative center of the image
var relCenter = {
x: (rect[0].left + rect[2].left) / 2,
y: (rect[0].top + rect[2].top) / 2
} ;
// calculate a vector from the center to the bottom left of the image
dotCorner = {
x: rect[1].left - (rect[1].left - relCenter.x) * 2 - relCenter.x,
y: rect[1].top - (rect[1].top - relCenter.y) * 2 - relCenter.y
} ;
// calculate a vector from the center to the mouse position
mousePos = {
x: e.clientX - relCenter.x,
y: e.clientY - relCenter.y
} ;
// calculate the angle between the two vector
theta = calculateAngle(dotCorner, mousePos) ;
// is the mouse-vector left of the dot-vector -> refer to the german math board
if(dotCorner.y*mousePos.x - dotCorner.x*mousePos.y > 0) {
theta *= -1 ;
}
// calculate new position of the dots and render them
overlaytiler.Rotater.prototype.rotateDot_(dots[2], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[1], theta, center);
overlaytiler.Rotater.prototype.rotateDot_(dots[0], theta, center);
// Render
this.render();
this.cx = e.clientX;
this.cy = e.clientY;
};
You can see that I wrote some function for vector calculations (just to make the code more readable):
function calculateScalarProduct(v1,v2)
{
return (v1.x * v2.x + v1.y * v2.y) ;
}
function calculateLength(v1)
{
return (Math.sqrt(v1.x*v1.x + v1.y*v1.y)) ;
}
function calculateAngle(v1, v2)
{
return (Math.acos(calculateScalarProduct(v1,v2) / (calculateLength(v1)*calculateLength(v2)))) ;
}
This is my working solution. Comment if you don't understand something, so I can make my answer more comprehensive.
Working example: JSBin
Wow, this was a tough one.

How to convert a point to polar coordinates in ECMAScript?

I need to turn a click location into a polar coordinate.
This is my current algorithm. Location is the location on the canvas of the click ({x:evt.clientX, y:evt.clientY}), center is the offset of the origin from 0,0. For example, if the circle is centered on 250, 250, center is {x:250, y:250}. Scale is the scale of the radius. For example, if the radius of a circle from the center would normally be 50 and the scale is .5, the radius becomes 25. (it's for zooming in/out)
this.getPolarLocation = function(location){
var unscaledFromCenter = {
x: location.x - center.x,
y: location.y - center.y
};
var angle = this.getAngleOnCircle(unscaledFromCenter);
var dist = Math.sqrt(unscaledFromCenter.x * unscaledFromCenter.x + unscaledFromCenter.y * unscaledFromCenter.y) * this.ds.scale;
return {
angle:angle,
dist:dist,
toString: function(){
return "Theta: ".concat(angle).concat("; dist: ").concat(dist);
}
};
}
this.getAngleOnCircle = function(location){
var x = location.x;
var y = location.y;
if(x == 0 && y > 0)
return Math.PI / 2;
if(x == 0 && y < 0)
return 3 * Math.PI / 2;
if(y == 0 && x > 0)
return 0;
if(y == 0 && x < 0)
return Math.PI;
var angle = Math.atan(y/x);
if(x > 0 && y > 0)
return angle;
if(x < 0)
return Math.PI + angle
return Math.PI * 2 + angle;
}
Screenshots of the issue. The left is what happens zoomed out (and is not supposed to happen). The right is zoomed in (scale >= 1), and is what is supposed to happen.
I'm under the impression that my center coordinates are being shifted slightly off. It seems to work fine for scale >= 1, but not for scale < 1
Source:
circos.html: http://pastie.org/private/cowsjz7mcihy8wtv4u4ag
circos.js: http://pastie.org/private/o9w3dwccmimalez9fropa
datasource.js: http://pastie.org/private/iko9bqq8eztbfh8xpvnoaw
Run in Firefox
So my question is: why doesn't this work?
For some reason, the program automagically works when I close firebug. It doesn't seem to work on Firefox 5, only the version I have (in the 3s somewhere). Either way, I'm scrapping the project for something more object oriented. There's no way the current algorithm could handle a genome. (which is exactly what I'm going to be mapping)
UPDATE:
I figured out the problem... I was measuring the distance from the top left of the page, not the top left of the canvas. Thus, when firebug was enabled, the screen was shifted, making the problems worse. The solution is the use canvas.offsetLeft and canvas.offsetTop to calculate the position on the canvas.

Categories

Resources