Get pivot cx / cy from rotation SVGMatrix - javascript

Given is an SVG element which has a rotation transformation among others.
Transformations can be iterated in JS, finding the rotation transformation by its type SVGTransform.SVG_TRANSFORM_ROTATE. The angle can be extracted using SVGTransform.angle.
Everything else is encoded in SVGTransform.matrix.
How to reconstruct the rotation center offset from the transform object?
console.clear();
let r = document.querySelector('svg rect');
let transforms = r.transform.baseVal;
for(let i=0; i<transforms.length; i++)
{
let t=transforms[i];
if(t.type==SVGTransform.SVG_TRANSFORM_TRANSLATE)
{
console.log('Found translate '+t.matrix.e+', '+t.matrix.f);
}
else if(t.type==SVGTransform.SVG_TRANSFORM_ROTATE)
{
console.log('Found rotation');
console.log(' - angle is '+t.angle); //30
console.log(' - cx is '+'???'); //50 wanted
console.log(' - cy is '+'???'); //25 wanted
}
}
<svg width="300" height="200">
<rect style="fill: blue;" width="100" height="50" transform="translate(30 30) rotate(30 50 25)" />
</svg>

After logging t it seems there is no straightforward way to get back cx and cy.
But we may calculate it from some equations.
The center of rotation is the only point that maps to itself under a rotation (if the angle is not zero or equivalent).
If the center is located at x,y , it needs to satisfy the following equation involving matrix multiplication:
One can solve the two equations a*x+c*y+e=x and b*x+d*y+f=y. After some further calculations it's possible to find x and y:
console.clear();
let r = document.querySelector('svg rect');
let transforms = r.transform.baseVal;
for(let i=0; i<transforms.length; i++)
{
let t=transforms[i];
if(t.type==SVGTransform.SVG_TRANSFORM_TRANSLATE)
{
console.log('Found translate '+t.matrix.e+', '+t.matrix.f);
}
else if(t.type==SVGTransform.SVG_TRANSFORM_ROTATE)
{
console.log('Found rotation');
let{a,b,c,d,e,f}= t.matrix ;
let denominator = a - a*d + b*c+d-1
let x = ((d-1)*e-c*f)/denominator
let y = ((a-1)*f-b*e)/denominator
console.log(t, 'angle' in t )
console.log(' - angle is '+t.angle); //30
console.log(' - cx is '+ x); // 50.00000000000001
console.log(' - cy is '+ y); // 25.000000000000004
}
}
<svg width="300" height="200">
<rect style="fill: blue;" width="100" height="50" transform="translate(30 30) rotate(30 50 25)" />
</svg>
As you can see, while the results are really close to the original, it can't exactly recover the input. But then you probably have no more than 4 decimal places in the input, you may want to use something like +cx.toFixed(4).
Also, if the angle is 0 or other multiples of 360 degrees, the center cannot be recovered, as such rotation is just identity matrix, any center gives the same results. Depending on your use case, it might be better to save the data about cx, cy etc. somewhere and write to the rectangle based on that, rather than getting the transforms from the rectangle and trying to recover them.

Related

d3 javascript, get distance between two points in an svg path

I have a set of points through which I have drawn a path.
let path=svg.append("path").attr("id","path")
.data([points])
.attr("d", d3.line()
.curve(d3.curveCatmullRom));
I now want to get the distance of the path between the points so that I can break it into segments. I have tried incrementing the distance and checking if the points (rounded off to 5) match with that of the initial set of points and thereby getting the distance whenever there is a match. I then save the points until there as a list.
Here, xyArray has the list of points to which I append d and the list seg as well.
function distanceBetween2Points(path, xyArray) {
let distance = 0;
xyArray.forEach(function(d,i)
{
let flag=1;
seg=[];
while(flag)
{let pt=path.getPointAtLength(distance);
if(round5(pt.x)==round5(d.x) && round5(pt.y)==round5(d.y))
{console.log("d",i);
d.d=distance;
d.seg=seg;
flag=0;
break;}
seg.push([pt.x,pt.y]);
distance++;}
return 0;
});
}
It sometimes works (even though not accurately) but sometimes does not, depending on the data. Is there a better way to get the distance?
This is a demo using vanilla javascript not d3 but I hope you'll find it useful.
The function getLengthForPoint(p,thePath)is calculating the distance on the path for a given point p.
I'm setting a variable let precision = 100;. Depending on the length of the path you may want to change this value to something else.
Also keep in mind that a path can pass through the same point multiple times. This can be tricky and can give you an error.
Also as you may know you will get the approximated distance to a point. In this example the point p1 = {x:93.5,y:60}. The point at the calculated length has this coords: {x:93.94386291503906,y: 59.063079833984375}
// some points on the path
let p1 = {x:93.5,y:60}
let p2 = {x:165,y:106}
//the total length of the path
let pathLength = thePath.getTotalLength();
let precision = 100;
let division = pathLength / precision;
function getLengthForPoint(p,thePath){
let theRecord = pathLength;
let theSegment;
for (let i = 0; i < precision; i++) {
// get a point on the path for thia distance
let _p = thePath.getPointAtLength(i * division);
// get the distance between the new point _p and the point p
let theDistance = dist(_p, p);
if (theDistance < theRecord) {
// if the distance is smaller than the record set the new record
theRecord = theDistance;
theSegment = i;
}
}
return(theSegment * division);
}
let theDistanceOnThePath = getLengthForPoint(p1,thePath);
//if you calculate the coords of a point at the calculated distance you'll see that is very near the point
console.log(thePath.getPointAtLength(theDistanceOnThePath));
let theDistanceBetween2PointsOnThePath = getLengthForPoint(p2,thePath) - getLengthForPoint(p1,thePath);
// a helper function to measure the distance between 2 points
function dist(p1, p2) {
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
svg{border:solid}
<svg viewBox="0 10 340 120">
<path id="thePath" fill="none" stroke="black" d="M10, 24Q10,24,40,67Q70,110,93.5,60Q117,10,123.5,76Q130,142,165,106Q200,70,235,106.5Q270,143, 320,24"></path>
<circle cx="93.5" cy="60" r="2" fill="red"/>
<circle cx="165" cy="106" r="2" fill="red"/>
</svg>
To get the distance between 2 points on the path you can do:
let theDistanceBetween2PointsOnThePath = getLengthForPoint(p2,thePath) - getLengthForPoint(p1,thePath);

Attribute `marker-mid` doesn't work when drawing an cubic bezier path

I am working on an graph drawing program currently. When I was writing codes, I decided to drew rings with cubic bezier path to those self-connected edge, and drew common ellipse arcs on edges that connected to other nodes. Besides, I was going to add labels to these edge using marker-mid attribute. But something quirky appeared, that the label doen't shown up on the self-connected edge (cubic bezier curve path), but it could show correctly on the edge that conntected to other nodes (ellipse arc path). I have been searched on the web for a while, but none of relative answers can solve my problem. I hope someone could tells me the reason. I will be very aappreciated.
Here is the code of marker. (Vue template)
<marker
:id="edge-label-marker-${edge.id}"
markerWidth='100'
markerHeight='9'
viewBox="-10 -20 40 30"
refX="0"
refY='10'
orient='0'
markerUnits='strokeWidth'>
<text text-anchor="middle">example label</text>
</marker>
and the code of path
<path :d='linkArc(edge)' :marker-mid="url(#edge-label-marker-${edge.id})"/>
Besides here are the codes that produce actual path of the curve:
function _getRingPath (x, y) {
var len = 300;
var controlPoints = {
left: { x: x - len, y: y - len },
right: { x: x + len, y: y - len },
};
return `
M ${x},${y}
C ${controlPoints.left.x},${controlPoints.left.y}
${controlPoints.right.x},${controlPoints.right.y}
${x},${y}`
}
function _getArcPath (sourceX, sourceY, targetX, targetY) {
var dx = sourceX - targetX
var dy = sourceY - targetY
var dr = Math.sqrt(dx * dx + dy * dy)
return `
M ${sourceX}, ${sourceY}
A ${dr},${dr} 0 0,1 ${targetX},${targetY}`
}
And finially, there is an example image that show the missing label.
The marker-mid should be rendered on every vertex other than the first and last vertices of the path data.
As you can see next it appears where it is supposed to apear.
path{stroke:black; fill:none}
<svg viewBox="0 0 250 200">
<marker
id="edge-label-marker"
markerWidth='100'
markerHeight='9'
viewBox="-10 -20 40 30"
refX="0"
refY='10'
orient='0'
markerUnits='strokeWidth'>
<text text-anchor="middle">example label</text>
</marker>
<path d="M10,95 C25,15 105,10 125,95 C145,180 225,185 240,95" marker-mid="url(#edge-label-marker)"/>
</svg>

How to compensate translate when skewX and skewY are used on SVG

CONTEXT: #AnaTudor (#daredevil) is talking about the d distance of movement for SVGs when skewX or skewY are used, so I was wondering if there is any way to calculate that distance in order to compensate with translate, and avoid using chained translate.
Test case: In the below snipet, we WON'T USE SVG specific chaining and/or nesting, we ONLY use same transform values, but in specific order of transform functions;
LEFT green rectangle via style: translate, rotate, skewX, skewY, scale;
RIGHT olive rectangle via transform attribute: translate, scale, rotate, skewX, `skewY.
Now, as you can see, the two rectangles have different positioning, if you click the button, the second rectangle gonna get closer to what we would expect, but still need to compute more for all cases.
Question: how can we change the fixOrigin function to adjust the translation for all possible transform function combinations, in a way that looks same as the CSS3 transform?
var el1 = document.querySelectorAll('path')[0],
el2 = document.querySelectorAll('path')[1],
el2BB = el2.getBBox(), el2cx = el2BB.x + el2BB.width/2, el2cy = el2BB.y + el2BB.height/2,
btn = document.querySelectorAll('button')[0], btn1 = document.querySelectorAll('button')[1],
x = 20, y = 20, scale = 0.6, rotate = 45, skewX = 20, skewY = -20;
el1.style.transform = 'translate(20px, 20px) rotate(45deg) skewX(20deg) skewY(-20deg) scale(0.6)';
el1.style.transformOrigin = '50% 50% 0px';
el2.setAttribute('transform', 'translate('+x+','+y+') scale('+scale+') rotate('+rotate+' '+el2cx+','+el2cy+') skewX('+skewX+') skewY('+skewY+')');
function fixOrigin(){
x += (1-scale) * el2BB.width/2;
y += (1-scale) * el2BB.height/2;
el2.setAttribute('transform', 'translate('+x+','+y+') scale('+scale+') rotate('+rotate+' '+el2cx+','+el2cy+') skewX('+skewX+') skewY('+skewY+')');
}
btn.addEventListener('click',fixOrigin,false);
function fixEverything() {
// scale binds all transform functions together
if ( !!scale ) {
//most important make sure we have translation values
//!!(x) && (x=0); !!(y) && (y=0);
// first adjust translate based on scale value
x += (1-scale) * el2BB.width/2;
y += (1-scale) * el2BB.height/2;
//now we also adjust the rotation transform origin based on SKEWS
if (!!rotate) {
// el2cx += .... el2cy += ...
}
//almost there, now we adjust the translation based on SKEWS
// x += ... y += ...
// last case, when SKEWS are used alone
} else if ( !scale && !rotate ) {
// adjust translation here
// x += ... y += ...
}
el2.setAttribute('transform', 'translate(' + x + ',' + y + ') scale(' + scale + ') rotate(' + rotate + ' ' + el2cx + ',' + el2cy + ') skewX(' + skewX + ') skewY(' + skewY + ')');
}
btn1.addEventListener('click', fixEverything, false);
/* desired transform
transform-origin: 50% 50% 0px;
transform: translate(20px, 20px) rotate(45deg) skewX(20deg) skewY(-20deg) scale(0.6);
*/
svg {
overflow: visible; width:30%;
border: 1px solid #eee; /* some sort of ruler */
}
<button>Fix Transform Origin</button><button>Fix All</button><br>
<p>Click to change the `transform` attribute</p>
<svg id="svgMixedCSS" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 500">
<path fill="green" d="M426.671 0h-341.328c-46.937 0-85.343 38.405-85.343 85.345v341.311c0 46.969 38.406 85.344 85.343 85.344h341.328c46.938 0 85.329-38.375 85.329-85.345v-341.31c0-46.94-38.391-85.345-85.329-85.345z" ></path>
</svg>
<svg id="svgMixedAttr" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 500">
<path fill="indigo" d="M426.671 0h-341.328c-46.937 0-85.343 38.405-85.343 85.345v341.311c0 46.969 38.406 85.344 85.343 85.344h341.328c46.938 0 85.329-38.375 85.329-85.345v-341.31c0-46.94-38.391-85.345-85.329-85.345z"></path>
</svg>
I made a super cool snipet for you to play around. UPDATE: Also it has a function draft with all possible cases.
Since your core question currently appears to be how to calculate that d distance. It comes from a talk where the skew operation is essentially described as (x, y) ↦ (x + d, y) for the case of skewX. If you look at the SVG spec you will find a matrix which essentially says that the exact formula for skewX(a) is (x, y) ↦ (x + cos(a) y, y). Likewise for skewY(a) you have (x, y) ↦ (x, y + cos(a) x). So I'd say that d := cos(a) * y in the first case and d := cos(a) * x in the second case.
The question title suggested a different question to me, though. That question could be formulated as follows: given transform="skewX(c) translate(a, b)" for some numbers a, b and c, find d and e such that the transformation I just gave is the same as translate(d, e) skewX(c). Or in other words: how do I have to change the entries of a transform if I want to move the transform to the outside of a skewX.
To find these numbers, look at the corresponding matrix products, as defined in the spec:
⎡1 tan(c) 0⎤ ⎡1 0 a⎤ ⎡1 tan(c) a + tan(c) b⎤ ⎡1 0 a + tan(c) b⎤ ⎡1 tan(c) 0⎤
⎢0 1 0⎥∙⎢0 1 b⎥ = ⎢0 1 b ⎥ = ⎢0 1 b ⎥∙⎢0 1 0⎥
⎣0 0 1⎦ ⎣0 0 1⎦ ⎣0 0 1 ⎦ ⎣0 0 1 ⎦ ⎣0 0 1⎦
So you'd have d = a + tan(c) * b and e = b. You simply apply the skew transformation to the translation vector. In other words:
skewX(c) translate(a, b) = translate(a + tan(c) * b, b) skewX(c)
You can do a similar computation for y and obtain:
skewY(c) translate(a, b) = translate(a, b + tan(c) * a) skewY(c)
If you have both skewX and skewY combined, you can move the translate out one step at a time, so that at each step you only have to deal with a single skew direction. If you want the opposite direction (i.e. move translate closer to the inside of a skew), use - tan(c) instead of + tan(c) in these formulas.
Your edited question and the example it contains makes it clearer that what you are really after is translate CSS3 style="transform: …" transformations into equivalent SVG transform="…" transformations. In particular in a way which allows for the CSS3 transform-origin: 50% 50% 0px which places the center of transformation at the center of the object, as opposed to the origin of the SVG coordinate system.
The snippet below demonstrates two ways to achieve this. One is fairly simple: First translate the center of the object (which you already computed in the snippet from your question) to the origin, then perform all the transformations, then translate the point back to its original coordinates. That's the object in the center, which has essentially
transform="translate(256,256)
translate(20, 20)
rotate(45) skewX(20) skewY(-20) scale(0.6)
translate(-256,-256)"
But in your question you wrote that you'd like to “avoid using chained translate”, which the above makes use of (in a sense). In order to avoid that, you can combine all the translate steps into one. The code below does that, moving translate steps to the outside i.e. to the beginning of the sequence. The end result is essentially
transform="translate(211.325,73.165)
rotate(45) skewX(20) skewY(-20) scale(0.6)"
except for the actual result having more digits for each of these numbers. Personally I think that the first approach is easier and cleaner, but the second is probably closer to what you had in mind.
One particular benefit is that the code iterates over the elementary transformations in the order in which they are given in the transformation description, so that users are free to give transformations in any order they like, and the translations can still get collected appropriately.
var el1 = document.querySelectorAll('path')[0],
el2 = document.querySelectorAll('path')[1],
el2BB = el2.getBBox(), el2cx = el2BB.x + el2BB.width/2, el2cy = el2BB.y + el2BB.height/2,
el3 = document.querySelectorAll('path')[2],
transform = 'translate(20px, 20px) rotate(45deg) skewX(20deg) skewY(-20deg) scale(0.6)';
el1.style.transform = transform;
el1.style.transformOrigin = '50% 50% 0px';
transform = 'translate('+el2cx+','+el2cy+') ' + transform.replace(/deg/g,'').replace(/px/g,'')+' translate('+(-el2cx)+','+(-el2cy)+')';
el2.setAttribute('transform', transform);
el3.setAttribute('transform', combineTranslates(transform));
function combineTranslates(transform) {
var ts = [], // will contain list of elementary transformations
r = /\s*([A-Za-z0-9]+\s*\([\-0-9.,\s]*\))/g,
match,
pos = 0, // used during tokenization
deg = Math.PI/180.0,
x = 0, y = 0, // translation gets accumulated here
tmp;
// Tokenize transform into individual elementary transformations
while (match = r.exec(transform)) {
if (match.index !== pos) throw Error('Invalid transform: ' + transform);
pos += match[0].length;
ts.push(match[1]);
}
// TODO: check that only whitespace remains after matches
//console.log(ts);
// Iterate over transformations from inside to outside
for (var i = ts.length - 1; i >= 0; --i) {
match = /([A-Za-z0-9]+)\s*\(([\-0-9.,\s]*)\)/.exec(ts[i]);
var op = match[1],
args = match[2].replace(/\s+/g, '').split(',').map(Number);
//console.log(op, args);
switch (op) {
// Apply given transformation to (x,y) vector
case 'translate':
x += args[0];
y += args[1];
ts.splice(i, 1); // Drop translate from ts array
break;
case 'rotate':
var angle = args[0]*deg,
cos = Math.cos(angle),
sin = Math.sin(angle);
tmp = cos*x - sin*y;
y = sin*x + cos*y;
x = tmp;
break;
case 'scale':
x *= args[0];
y *= (args.length === 1 ? args[0] : args[1]);
break;
case 'skewX':
x += y*Math.tan(args[0]*deg);
break;
case 'skewY':
y += x*Math.tan(args[0]*deg);
break;
default:
throw Error('Unknown transform ' + op)
}
}
ts.unshift('translate('+x+','+y+')'); // add as first element
//console.log('From '+transform+'\n to '+ts.join(' '));
return ts.join(' ');
};
svg { overflow: visible; width:30%; border: 1px solid #eee; }
<svg id="svgMixedCSS" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 500">
<path fill="green" d="M426.671 0h-341.328c-46.937 0-85.343 38.405-85.343 85.345v341.311c0 46.969 38.406 85.344 85.343 85.344h341.328c46.938 0 85.329-38.375 85.329-85.345v-341.31c0-46.94-38.391-85.345-85.329-85.345z" ></path>
</svg>
<svg id="svgMixedAttr" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 500">
<path fill="indigo" d="M426.671 0h-341.328c-46.937 0-85.343 38.405-85.343 85.345v341.311c0 46.969 38.406 85.344 85.343 85.344h341.328c46.938 0 85.329-38.375 85.329-85.345v-341.31c0-46.94-38.391-85.345-85.329-85.345z"></path>
</svg>
<svg id="svgCombinedAttr" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 500">
<path fill="blue" d="M426.671 0h-341.328c-46.937 0-85.343 38.405-85.343 85.345v341.311c0 46.969 38.406 85.344 85.343 85.344h341.328c46.938 0 85.329-38.375 85.329-85.345v-341.31c0-46.94-38.391-85.345-85.329-85.345z"></path>
</svg>

Javascript: How to determine a SVG path draw direction?

I'm trying to determine a SVG path draw orientation. I'm working on something like this
var length = path.getTotalLength();
var horizontal = path.getPointAtLength(length/4).x - path.getPointAtLength(0).x;
var vertical = path.getPointAtLength(length/4).y - path.getPointAtLength(0).y;
Then do some comparisons with these values horizontal > 0 and vertical > 0, but this above idea isn't, in my mind, very successful.
My question is: is there anything I can use to determine the draw direction or perhaps some built in SVG methods/options?
Thank you
Use Math.atan2(yDiff, xDiff) to get the angle between the two reference points. Two visually identical shapes that go in opposite directions will have an angle difference of pi.
Be aware of the edge case where your two reference points are unluckily the same point. Not likely, especially given rounding errors, but keep it in mind in case you need this to be rock solid.
var paths = document.getElementsByTagName("path");
for (var pathNum = 0; pathNum < paths.length; pathNum += 1) {
var path = paths[pathNum];
var message = document.createElement('p');
message.innerHTML = "path #" + pathNum + ": angle = " + pathDir(path);
document.body.appendChild(message);
};
function pathDir(path) {
var length = path.getTotalLength();
var pt14 = path.getPointAtLength(1/4 * length);
var pt34 = path.getPointAtLength(3/4 * length);
var angle = Math.atan2(pt14.y - pt34.y, pt14.x - pt34.x);
return angle;
}
<svg width="300" height="80">
<g fill="none" stroke="black" stroke-width="4">
<path d="M 10,10 C 90,10 -30,60 50,60Z"/>
<path d="M110,10 C190,10 70,60 150,60Z"/>
<path d="M250,60 C170,60 290,10 210,10Z"/>
</g>
</svg>
<div></div>

Get angle in terms of 360 degrees

I would like to get an angle in terms of 360 degrees... for my game, I need to know which direction the player is heading in...
The code here gets the proper angles, but only in terms of 90 degree increments: (meaning, when I click in upper left quadrant, I get angle from 0 to 90 degrees... bottom left is 0 to -90 degrees, etc...)
var dY = this.pos.y-e.gameY; //opposite
var dX = this.pos.x-e.gameX; //adjacent
var dist = Math.sqrt((dY*dY)+(dX*dX)); //hypotenuse
var sin = dY/dist; //opposite over hypotenuse
var radians = Math.asin(sin);
var degrees = radians*(180/Math.PI); //convert from radians to degrees
this.calculatedAngle = degrees;
How can I get it in terms of 360 degrees?
Here is another example: The top two represent the issue... when I click in the upper/lower left quadrant, it keeps drawing a right triangle from the x axis...
I need it to be like the lower 2 pictures, where it keeps drawing the angle around:
You can do this directly from the coordinates, without computing extra information like the hypotenuse, by using the atan2 function, which was designed in the early days of FORTRAN for situations exactly like yours.
Note two important things:
that the atan2 function has been created to automatically handle all but one case of the many possible cases, and
it will output a range of (-PI, PI].
The case where both coordinates are (0, 0) is left undefined (all angles are equivalent when the magnitude of a vector is zero), so I'm arbitrarily setting the angle to zero degrees in that case. And in order to obtain the desired range, some simple logic and addition is needed.
var Vx = this.pos.x - e.gameX;
var Vy = this.pos.y - e.gameY;
var radians;
if (Vx || Vy) {
radians = Math.atan2(Vy, Vx);
} else {
radians = 0;
}
if (radians < 0) {
radians += 2*Math.PI;
}
var degrees = radians * 180 / Math.PI;
this.calculatedAngle = degrees;
The result will be an angle defined for all cases and within the range [0, 360°), as desired.
Example
function showDegrees(e, svg) {
var rectangle = svg.getBoundingClientRect();
var targetX = (rectangle.left + rectangle.right)/2;
var targetY = (rectangle.top + rectangle.bottom)/2;
var Vx = Math.round(e.clientX - targetX);
var Vy = Math.round(targetY - e.clientY);
var radians = Math.atan2(Vy, Vx);
if (radians < 0) radians += 2*Math.PI;
var degrees = Math.round(radians*180/Math.PI);
var textBox = document.getElementById('showdegrees');
textBox.innerHTML = degrees + '°' + ' (' + Vx + ', ' + Vy + ')';
textBox.setAttribute('x', Math.round(100 + Vx));
textBox.setAttribute('y', Math.round(100 - Vy));
}
<svg width="200" height="200" viewBox="0 0 200 200" onmousemove="showDegrees(evt, this)">
<text x="0" y="0" fill="red" style="font-size: 12px" id="showdegrees">Info</text>
<line x1="100" y1="0" x2="100" y2="200" style="stroke: black; stroke-width: 1" />
<line x1="0" y1="100" x2="200" y2="100" style="stroke: black; stroke-width: 1" />
</svg>
Try this:
var dY = this.pos.y - e.gameY, // opposite
dX = this.pos.x - e.gameX, // adjacent
radians = Math.atan(dY/dX); // wrong, in [-1/2 pi, 1/2 pi]
if(1/dX < 0) radians += Math.PI; // fixed, in [-1/2 pi, 3/2 pi]
if(1/radians < 0) radians += 2*Math.PI; // fixed, in [+0, 2 pi]
var degrees = radians*180/Math.PI; // from radians to degrees
Explanation:
Better calculate the radians with Math.atan and the tangent. Calculating the hypotenuse using Math.sqrt is expensive.
Math.atan gives an angle between -1/2 pi and 1/2 pi, that is, the right half circle. To fix it, just sum pi in case dX was negative.
Then we get an angle between -1/2 pi and 3/2 pi. So, in case it's negative, we sum 2 pi in order to obtain an angle between 0 and 2 pi.
Note that we must consider -0 negative in order to make it work properly. So, instead of checking dX < 0 and radians < 0, we check their inverse.
Note the final result will be NaN in case both dX and dY are 0 (or -0).
You need to use a little bit more information than just the arcsin to figure this out. The reason for this is that arcsin will only return values between -π/2 and π/2 (-90 degrees and 90 degrees), as you already know.
So, to figure out the other part, you need to be aware of what quadrant you are in.
In quadrant 2, arcsin is between 90 and 0, the real angle is between 90 and 180. So if you are in quadrant 2, 180-calculated angle=real angle. (180-90=90, 180-0=180).
In quadrant 3, arcsin is between 0 and -90. The real angle is between 180 and 270. so again, 180-calculated angle = real angle. (180-0=180, 180-(-90)=270).
In quadrant 4, arcsin is between -90 and 0. The real angle is between 270 and 360. So 360+calculated angle=real angle. (360+(-90)=270, 360+0)=360).
The same is also true for quadrant 1, you just don't need to make the transformation. (0+360=360 (equivalent to 0), 90+360=450 (equivalent to 90) ).
So, determine the quadrant first, and apply a rule based on that.
For an (x,y) coordinate, if x is positive and y is positive, you are quadrant 1. If x is negative and y is positive, you are quadrant 2. If x is negative and y is negative, you are quadrant 3. if x is positive and y is negative, you are quadrant 4.
So in your case, the distance from the origin is you (dX,dY) coordinate pair, so something like this should do it:
//your original code...
var degrees = radians*(180/Math.PI); //convert from radians to degrees
//then
if (dX>=0 and dY>=0)
{//quadrant 1
//no transformation, do nothing
}
if (dX>=0 and dy<0)
{ //quadrant 4
degrees=360+degrees;
}
if (dX<0 and dy<0)
{ //quadrant 3
degrees=180-degrees;
}
if (dX<0 and dy>0)
{ //quadrant 2
degrees=180-degrees;
}
this.calculatedAngle = degrees;

Categories