convert 3d coordinates to 2d screen coordinates (plane mesh) - javascript

Hello I have a plane in three.js with width 5000 and height 5000. With the following code I convert 3d coordinates into 2d, more specific I get the top left point of the plane and the bottom right.
I initialize my camera with position x: 0, y: 0, z: 70000 and rotation x:0,y:0,z:0.
The coordinates I get if I don't rotate my camera (using orbit controls) are correct:
however if I rotate my camera the coordinates are not correct and I cant figure out why.
Here is my code so far:
var vec3 = new THREE.Vector3();
vec3.set( _that.model.position.x - 2500, _that.model.position.y + 2500, _that.model.position.z ); // top left corner
vec3.project( _that.vise.camera );
var percX = Math.abs(vec3.x + 1) / 2;
var percY = Math.abs(-vec3.y + 1) / 2;
this.topLeft = {
x : percX * _that.vise.options.generic.container.clientWidth,
y : percY * _that.vise.options.generic.container.clientHeight
}
var vec4 = new THREE.Vector3();
vec4.set( _that.model.position.x + 2500, _that.model.position.y - 2500, _that.model.position.z ); // top left corner
vec4.project( _that.vise.camera );
var percX = Math.abs(vec4.x + 1) / 2;
var percY = Math.abs(-vec4.y + 1) / 2;
this.bottomRight = {
x : percX * _that.vise.options.generic.container.clientWidth,
y : percY * _that.vise.options.generic.container.clientHeight
}
this.projectedVector = {
x : Math.abs(this.topLeft.x - this.bottomRight.x),
y : Math.abs(this.topLeft.y - this.bottomRight.y)
}

Take a look on this:
http://www.threejsgames.com/extensions/#threex.objcoord
It includes a translation of 3D to Screen Position, is this what you want?

Related

How do I rotate a html canvas shape by only using a transform?

I am wondering how you can rotate an image by only using the transform function. From my understanding this is not possible, since the only things you can do with transform are the following:
Horizontal scaling
Horizontal skewing
Vertical skewing
Vertical scaling
Horizontal moving
Vertical moving
Source: https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Transformations
And I don't see how any of these would be able to rotate the shape, is this even possible. I assume it must be possible, since rotate is in-fact a type transformation.
2D transform basics
6 values as 3 vectors
The transform is a set of 6 numbers. The 6 numbers as 3 pairs represent the direction and scale of the x axis, the direction and scale of the y axis, and the position of the origin.
Default transform AKA Identity matrix
The default transform (called the identity matrix) has the values ctx.setTransform(1, 0, 0, 1, 0, 0) meaning that
the x axis is 1 transformed pixel per CSS pixel in the direction {x: 1, y: 0} left to right
the y axis is 1 transformed pixel per CSS pixel in the direction {x: 0, y: 1} top to bottom
the origin is at pixel location {x: 0, y: 0} top left corner
Scaling
If we scale the transform we increase the length of the first two vectors. To scale by 2 the transform is ctx.setTransform(2, 0, 0, 2, 0, 0);
the x axis is 1 transformed pixel for every 2 CSS pixel in the x direction {x: 2, y: 0} left to right
the y axis is 1 transformed pixel for every 2 CSS pixel in the y direction {x: 0, y: 2} top to bottom
the origin is still top left {x: 0, y: 0}
Rotate and translation
If we want to rotate by 90deg a square 256 by 256 image then the transform is ctx.setTransform(0, 1, -1, 0, 256, 0)
the x axis is 1 transformed pixel per CSS pixel down in the y direction {x: 0, y: 1}
the y axis is 1 transformed pixel per CSS pixel across in the negative x direction {x: -1, y: 0} right to left
the origin (where the image 0, 0 will be on the canvas) is {x: 256, y: 0}
Thus if we run
ctx.setTransform(0, 1, -1, 0, 256, 0);
ctx.drawImage(myImage, 0, 0, 256, 256); // will draw image rotated 90deg CW
We get a rotated image.
A vector
A vector is two values that have a x and y value. The vector defines a direction and length.
Create a rotated unit vector
To convert a direction to a vector we use sin and cos
const myDirection = angle;
const myDirectionAsRadians = angle * (Math.PI / 180); // convert angle to radians
const x = Math.cos(myDirectionAsRadians)
const y = Math.sin(myDirectionAsRadians)
If we set myDirection to 90 (deg) then x = 0 and y = 1 pointing down the canvas
Using sin and cos creates a vector in any direction. It has a special property in that its length is always 1. We call such a vector a Unit vector. You may sometimes see a vector being normalized. This converts a vector of any length to a unit vector. It is done by dividing the vector x and y by its length.
function normalize(vector) {
const length = Math.hypot(vector.x, vector.y);
vector.x /= length;
vector.y /= length;
}
NOTE a vector with zero length eg x: 0, y:0 can not be normalized. Not because it has no length (the length is 0) but because it has no direction.
Scale a rotated vector
We can define an angle and a scale
const myDirection = -90;
const myDirectionAsRadians = -90 * (Math.PI / 180); // -90 as radians
const myScale = 2;
const x = Math.cos(myDirectionAsRadians) * myScale
const y = Math.sin(myDirectionAsRadians) * myScale
Now for -90 deg the vector is x = 0 and y = -2 pointing up and two CSS pixels long.
Quick rotate vector 90deg CW
For a uniform scale and rotation (the image is always square) all we need is a single vector. For example from the above. x = 0 and y = -2 (pointing up) can be rotated 90 CW by swapping the two components and negating the new x. eg xx = -y and y = x to get xx = 2 and y = 0 2 CSS pixels from left two right. Thus we have the direction and scale of both the x and y axis. With the y axis always 90 CW from the x.
Using the transform to draw
Create rotated and scaled transform
To create a transform that rotates any angle and scales by any amount
function scaleAndRotate(scale, rotate) { // rotate is in radians
// get direction and length of x axis
const xAX = Math.cos(rotate) * scale;
const xAY = Math.sin(rotate) * scale;
// get direction and length of y axis that is 90 deg CW of x axis and same length
const [yAX, yAY] = [-xAY, xAX]; // swap and negate new x
// set the transform
ctx.setTransform(xAX, xAY, yAX, yAY, 0, 0);
}
Drawing an image
Lets create a function that will draw an image anywhere on the canvas that is rotated and scaled uniformly. We will use the center of the image as the reference point
function drawImageScaleRotate(img, x, y, scale, rotate) {
// define the direction and scale of x axis
const xAX = Math.cos(rotate) * scale;
const xAY = Math.sin(rotate) * scale;
// create the transform with yaxis at 90 CW of x axis and origin at x, y
ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);
// Draw the image so that its center is at the new origin x, y
ctx.drawImage(img, -img.width / 2, -img.height / 2);
}
There is much more
When we set the transform with ctx.setTranform we replace the existing transform. This transform remains current. If we use ctx.transform, ctx.rotate, ctx.scale, ctx.translate the transforms are applied to the current transform, you build a transform in stages.
The transform functions are relatively expensive in terms of CPU cycles. That is way using sin and cos to build the matrix is much faster than using ctx.scale, ctx.rotate, ctx.translate to do the same thing starting from default.
Building transforms can become tricky as we need to keep track of what stage we are at.
We generally only use these function not to transform a single image (text, path, or what ever) but to create linked transforms.
For example a game object like a tank. The body of the tank is transformed (rotated and positioned) then the turret which is rotated with the body but has an additional independent rotation by using ctx.rotate. Full explanation is beyond the scope of this question.
The final function
From all this we can create a simplified function that will draw an image with its center at any location, that is uniformly scaled and rotated
function drawImageScaleRotate(img, x, y, scale, rotate) {
const xAX = Math.cos(rotate) * scale;
const xAY = Math.sin(rotate) * scale;
ctx.setTransform(xAX, xAY, -xAY, xAX, x, y);
ctx.drawImage(img, -img.width / 2, -img.height / 2);
}
To reset the transform to the default use ctx.resetTransform NOTE not fully supported yet or use ctx.setTransform(1,0,0,1,0,0);
Animated transforms faster than CSS + HTML or SVG
Using the above function is the 2nd fastest way to draw animated rotated scaled images, faster than CSS + HTML or SVG. You can literally fill the screen with animated images.
Demo
var w,h;
var image = new Image;
image.src = "https://i.stack.imgur.com/C7qq2.png?s=328&g=1";
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
document.body.appendChild(canvas);
const resize = () => { w = canvas.width = innerWidth; h = canvas.height = innerHeight;}
const rand = (min,max) => Math.random() * (max ?(max-min) : min) + (max ? min : 0);
const DO = (count,callback) => { while (count--) { callback(count) } }
resize();
addEventListener("resize",resize);
const sprites = [];
DO(500,()=>{
sprites.push({
xr : rand(w), yr : rand(h),
x : 0, y : 0, // actual position of sprite
r : rand(Math.PI * 2),
scale : rand(0.1,0.25),
dx : rand(-2,2), dy : rand(-2,2),
dr : rand(-0.2,0.2),
});
});
function drawImage(image, spr){
const xAX = Math.cos(spr.r) * spr.scale;
const xAY = Math.sin(spr.r) * spr.scale;
ctx.setTransform(xAX, xAY, -xAY, xAX, spr.x, spr.y);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
}
function update(){
var ihM,iwM;
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,w,h);
if(image.complete){
var iw = image.width;
var ih = image.height;
for(var i = 0; i < sprites.length; i ++){
var spr = sprites[i];
spr.xr += spr.dx;
spr.yr += spr.dy;
spr.r += spr.dr;
// keeps images in canvas adds space to all sides so that image
// can move completely of the canvas befor warping to other side
// I do this to prevent images visualy popping in and out at edges
iwM = iw * spr.scale * 2 + w;
ihM = ih * spr.scale * 2 + h;
spr.x = ((spr.xr % iwM) + iwM) % iwM - iw * spr.scale;
spr.y = ((spr.yr % ihM) + ihM) % ihM - ih * spr.scale;
drawImage(image,spr);
}
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
If you are wondering which is the fastest way to draw animated content. That is via webGL. The above can draw 1000 scaled rotated images on most devices at a good frame rate. WebGL can easily draw 10000 (with extra features eg colored) in the same time.
You can rotate your image easily. You can specify angles by which you wants to apply rotation.
Source : https://www.w3schools.com/cssref/css3_pr_transform.asp
<style>
img.a {
transform: rotate(180deg);
}
</style>
<img class="a" src="https://picsum.photos/id/237/200/300"/>
use transform "rotate".and use it as below
p{
color:red;
font-size:12px;
text-align:center;
}
.rotate1{
transform:rotate(45deg);
margin-top:40px;
}
.rotate2{
transform:rotate(90deg);
margin-top:40px;
}
.rotate3{
transform:rotate(180deg);
margin-top:40px;
}
<p class="rotate1">ROTATE1</p>
<p class="rotate2">ROTATE2</p>
<p class="rotate3">ROTATE3</p>
You have ctx.rotate(radians) function.
Read below:
https://www.w3schools.com/tags/canvas_rotate.asp

HTML5 Canvas Grid Tilt [duplicate]

I was trying to do a perspective grid on my canvas and I've changed the function from another website with this result:
function keystoneAndDisplayImage(ctx, img, x, y, pixelHeight, scalingFactor) {
var h = img.height,
w = img.width,
numSlices = Math.abs(pixelHeight),
sliceHeight = h / numSlices,
polarity = (pixelHeight > 0) ? 1 : -1,
heightScale = Math.abs(pixelHeight) / h,
widthScale = (1 - scalingFactor) / numSlices;
for(var n = 0; n < numSlices; n++) {
var sy = sliceHeight * n,
sx = 0,
sHeight = sliceHeight,
sWidth = w;
var dy = y + (sliceHeight * n * heightScale * polarity),
dx = x + ((w * widthScale * n) / 2),
dHeight = sliceHeight * heightScale,
dWidth = w * (1 - (widthScale * n));
ctx.drawImage(img, sx, sy, sWidth, sHeight,
dx, dy, dWidth, dHeight);
}
}
It creates almost-good perspective grid, but it isn't scaling the Height, so every square has got the same height. Here's a working jsFiddle and how it should look like, just below the canvas. I can't think of any math formula to distort the height in proportion to the "perspective distance" (top).
I hope you understand. Sorry for language errors. Any help would be greatly appreciatedRegards
There is sadly no proper way besides using a 3D approach. But luckily it is not so complicated.
The following will produce a grid that is rotatable by the X axis (as in your picture) so we only need to focus on that axis.
To understand what goes on: We define the grid in Cartesian coordinate space. Fancy word for saying we are defining our points as vectors and not absolute coordinates. That is to say one grid cell can go from 0,0 to 1,1 instead of for example 10,20 to 45, 45 just to take some numbers.
At the projection stage we project these Cartesian coordinates into our screen coordinates.
The result will be like this:
ONLINE DEMO
Ok, lets dive into it - first we set up some variables that we need for projection etc:
fov = 512, /// Field of view kind of the lense, smaller values = spheric
viewDist = 22, /// view distance, higher values = further away
w = ez.width / 2, /// center of screen
h = ez.height / 2,
angle = -27, /// grid angle
i, p1, p2, /// counter and two points (corners)
grid = 10; /// grid size in Cartesian
To adjust the grid we don't adjust the loops (see below) but alter the fov and viewDist as well as modifying the grid to increase or decrease the number of cells.
Lets say you want a more extreme view - by setting fov to 128 and viewDist to 5 you will get this result using the same grid and angle:
The "magic" function doing all the math is as follows:
function rotateX(x, y) {
var rd, ca, sa, ry, rz, f;
rd = angle * Math.PI / 180; /// convert angle into radians
ca = Math.cos(rd);
sa = Math.sin(rd);
ry = y * ca; /// convert y value as we are rotating
rz = y * sa; /// only around x. Z will also change
/// Project the new coords into screen coords
f = fov / (viewDist + rz);
x = x * f + w;
y = ry * f + h;
return [x, y];
}
And that's it. Worth to mention is that it is the combination of the new Y and Z that makes the lines smaller at the top (at this angle).
Now we can create a grid in Cartesian space like this and rotate those points directly into screen coordinate space:
/// create vertical lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(i, -grid);
p2 = rotateX(i, grid);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]); //from easyCanvasJS, see demo
}
/// create horizontal lines
for(i = -grid; i <= grid; i++) {
p1 = rotateX(-grid, i);
p2 = rotateX(grid, i);
ez.strokeLine(p1[0], p1[1], p2[0], p2[1]);
}
Also notice that position 0,0 is center of screen. This is why we use negative values to get out on the left side or upwards. You can see that the two center lines are straight lines.
And that's all there is to it. To color a cell you simply select the Cartesian coordinate and then convert it by calling rotateX() and you will have the coordinates you need for the corners.
For example - a random cell number is picked (between -10 and 10 on both X and Y axis):
c1 = rotateX(cx, cy); /// upper left corner
c2 = rotateX(cx + 1, cy); /// upper right corner
c3 = rotateX(cx + 1, cy + 1); /// bottom right corner
c4 = rotateX(cx, cy + 1); /// bottom left corner
/// draw a polygon between the points
ctx.beginPath();
ctx.moveTo(c1[0], c1[1]);
ctx.lineTo(c2[0], c2[1]);
ctx.lineTo(c3[0], c3[1]);
ctx.lineTo(c4[0], c4[1]);
ctx.closePath();
/// fill the polygon
ctx.fillStyle = 'rgb(200,0,0)';
ctx.fill();
An animated version that can help see what goes on.

find position of a point with origin, angle and radius

im stuck with a trigonometry problem in a javascript game im trying to make.
with a origin point(xa,ya) a radius and destination point (ya,yb) I need to find the position of a new point.
//calculate a angle in degree
function angle(xa, ya, xb, yb)
{
var a= Math.atan2(yb - ya, xb - xa);
a*= 180 / Math.PI;
return a;
}
function FindNewPointPosition()
{
//radius origine(xa,xb) destination(ya,yb)
var radius=30;
var a = angle(xa, xb, ya, yb);
newpoint.x = xa + radius * Math.cos(a);
newpoint.y = ya + radius * Math.sin(a);
return newpoint;
}
Imagine a image because I dont have enough reputation to post one :
blue square is the map (5000x5000), black square (500x500) what players see (hud).
Cross(400,400) is the origin and sun(4200,4200) the destination.
The red dot (?,?) indicate to player which direction take to find the sun ..
But sun and cross position can be reverse or in different corner or anywhere !
At the moment the red dot do not do that at all ..
Tks for your help.
Why did you use ATAN2? Change to Math.atan() - you will get angle in var A
Where you have to place your red dot? inside hud?
Corrected code
https://jsfiddle.net/ka9xr07j/embedded/result/
var obj = FindNewPointPosition(400,4200,400,4200); - new position 417. 425
Finally I find a solution without using angle.
function newpointposition(origin, destination)
{
// radius distance between cross and red dot
var r=30;
// calculate a vector
var xDistance = destination.x - origin.x;
var yDistance = destination.y - origin.y;
// normalize vector
var length = Math.sqrt(xDistance * xDistance + yDistance * yDistance);
xDistance /= length;
yDistance /= length;
// add the radius
xDistance = xDistance * r;
yDistance = yDistance * r;
var newpoint = { x: 0, y: 0 };
newpoint.x = origin.x + xDistance;
newpoint.y = origin.y + yDistance;
return newpoint;
}
var radar = newpointposition({
x: 500,
y: 800
}, {
x: 3600,
y: 2850
});
alert(radar.x + ' ' + radar.y);
ty Trike, using jsfiddle really help me.

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.

Rotating canvas around a point and getting new x,y offest

I am writing a multitouch jigsaw puzzle using html5 canvas in which you can rotate the pieces around a point. Each piece has their own canvas the size of their bounding box. When the rotation occurs, the canvas size must change, which I am able to calculate and is working. What I can't figure out, is how to find the new x,y offsets if I am to have this appear to be rotating around the pivot (first touch point).
Here is an image to better explain what I'm trying to achieve. Note the pivot point is not always the center, otherwise I could just halve the difference between the new bounds and the old.
So I know the original x, y, width, height, rotation angle, new bounds(rotatedWidth, rotatedHeight), and the pivot X,Y relating to original object. What I can't figure out how to get is the x/y offset for the new bounds (to make it appear that the object rotated around the pivot point)
Thanks in advance!
First we need to find the distance from pivot point to the corner.
Then calculate the angle between pivot and corner
Then calculate the absolute angle based on previous angle + new angle.
And finally calculate the new corner.
Snapshot from demo below showing a line from pivot to corner.
The red dot is calculated while the rectangle is rotated using
translations.
Here is an example using an absolute angle, but you can easily convert this into converting the difference between old and new angle for example. I kept the angles as degrees rather than radians for simplicity.
The demo first uses canvas' internal translation and rotation to rotate the rectangle. Then we use pure math to get to the same point as evidence that we have calculated the correct new x and y point for corner.
/// find distance from pivot to corner
diffX = rect[0] - mx; /// mx/my = center of rectangle (in demo of canvas)
diffY = rect[1] - my;
dist = Math.sqrt(diffX * diffX + diffY * diffY); /// Pythagoras
/// find angle from pivot to corner
ca = Math.atan2(diffY, diffX) * 180 / Math.PI; /// convert to degrees for demo
/// get new angle based on old + current delta angle
na = ((ca + angle) % 360) * Math.PI / 180; /// convert to radians for function
/// get new x and y and round it off to integer
x = (mx + dist * Math.cos(na) + 0.5)|0;
y = (my + dist * Math.sin(na) + 0.5)|0;
Initially you can verify the printed x and y by seeing that the they are exact the same value as the initial corner defined for the rectangle (50, 100).
UPDATE
It seems as I missed the word in: offset for the new bounds... sorry about that, but what you can do instead is to calculate the distance to each corner instead.
That will give you the outer limits of the bound and you just "mix and match" the corner base on those distance values using min and max.
New Live demo here
The new parts consist of a function that will give you the x and y of a corner:
///mx, my = pivot, cx, cy = corner, angle in degrees
function getPoint(mx, my, cx, cy, angle) {
var x, y, dist, diffX, diffY, ca, na;
/// get distance from center to point
diffX = cx - mx;
diffY = cy - my;
dist = Math.sqrt(diffX * diffX + diffY * diffY);
/// find angle from pivot to corner
ca = Math.atan2(diffY, diffX) * 180 / Math.PI;
/// get new angle based on old + current delta angle
na = ((ca + angle) % 360) * Math.PI / 180;
/// get new x and y and round it off to integer
x = (mx + dist * Math.cos(na) + 0.5)|0;
y = (my + dist * Math.sin(na) + 0.5)|0;
return {x:x, y:y};
}
Now it's just to run the function for each corner and then do a min/max to find the bounds:
/// offsets
c2 = getPoint(mx, my, rect[0], rect[1], angle);
c2 = getPoint(mx, my, rect[0] + rect[2], rect[1], angle);
c3 = getPoint(mx, my, rect[0] + rect[2], rect[1] + rect[3], angle);
c4 = getPoint(mx, my, rect[0], rect[1] + rect[3], angle);
/// bounds
bx1 = Math.min(c1.x, c2.x, c3.x, c4.x);
by1 = Math.min(c1.y, c2.y, c3.y, c4.y);
bx2 = Math.max(c1.x, c2.x, c3.x, c4.x);
by2 = Math.max(c1.y, c2.y, c3.y, c4.y);
to rotate around the centre point of the canvas you can use this function:
function rotate(context, rotation, canvasWidth, canvasHeight) {
// Move registration point to the center of the canvas
context.translate(canvasWidth / 2, canvasHeight/ 2);
// Rotate 1 degree
context.rotate((rotation * Math.PI) / 180);
// Move registration point back to the top left corner of canvas
context.translate(-canvasWidth / 2, -canvasHeight/ 2);
}
Here is the way that worked best for me. First I calculate what is the new width and height of that image, then I translate it by half of that amount, then I apply the rotation and finally I go back by the original width and height amount to re center the image.
var canvas = document.getElementById("canvas")
const ctx = canvas.getContext('2d')
drawRectangle(30,30,40,40,30,"black")
function drawRectangle(x,y,width,height, angle,color) {
drawRotated(x,y,width,height,angle,ctx =>{
ctx.fillStyle = color
ctx.fillRect(0,0,width,height)
})
}
function drawRotated(x,y,width,height, angle,callback)
{
angle = angle * Math.PI / 180
const newWidth = Math.abs(width * Math.cos(angle)) + Math.abs(height * Math.sin(angle));
const newHeight = Math.abs(width * Math.sin(angle)) + Math.abs(height * Math.cos(angle));
var surface = document.createElement('canvas')
var sctx = surface.getContext('2d')
surface.width = newWidth
surface.height = newHeight
// START debug magenta square
sctx.fillStyle = "magenta"
sctx.fillRect(0,0,newWidth,newHeight)
// END
sctx.translate(newWidth/2,newHeight/2)
sctx.rotate(angle)
sctx.translate(-width/2,-height/2)
callback(sctx)
ctx.drawImage(surface,x-newWidth/2,y-newHeight/2)
}
#canvas{
border:1px solid black
}
<canvas id="canvas">
</canvas>

Categories

Resources