I have a div that is getting rotated. I actually want to know it's orginal left and top coordinates before it was rotated.
I've been looking at different formulas but i think i need someone to help me a bit.
Thanks,
<html>
<body>
<div id="test" style="width:50px;height:50px;background-color:yellow;tranform:rotate(45deg);transform-orgin:top left">
</div>
</body>
</html>
As a formula, i found this one.
function rotatedPosition(pLeft, pTop, oLeft, oTop, angle){
// 1
var x = pLeft - oLeft;
var y = pTop - oTop;
// 2
var xRot = x * Math.cos(angle) - y * Math.sin(angle);
var yRot = x * Math.sin(angle) + y * Math.cos(angle);
// 3
var pLeftRot = xRot + oLeft;
var pTopRot = yRot + oTop
return {left: pLeftRot, top: pTopRot};
}
This will calculate the rotated coordinates. I tried to modifiy it so that it would calculate the original coordinates in stead of the rotated coordinates. But that kind of math is to long ago for me.
It is using a css transfrom: rotate(45deg); tranform-origin: top left;. I'm getting this div already rotated so i don't know it's unrotated coordinates. in the end i need to get the top and left position of the div as it would appear unrotated.
Ok, that's it... store the top and left values before calling rotatedPosition function.
By the way, I would recommend you to use let and const instead of var within the function body. That way those identifiers will only exist in there.
<html>
<script>
var original_Top = -1, original_Left = -1;
function rotatedPosition(pLeft, pTop, oLeft, oTop, angle){
original_Top = pTop;
original_Left = pLeft;
// 1
let x = pLeft - oLeft;
let y = pTop - oTop;
// 2
let xRot = x * Math.cos(angle) - y * Math.sin(angle);
let yRot = x * Math.sin(angle) + y * Math.cos(angle);
// 3
let pLeftRot = xRot + oLeft;
let pTopRot = yRot + oTop
return {left: pLeftRot, top: pTopRot};
}
</script>
<body>
<div id="test" style="width:50px;height:50px;background-color:yellow">
</div>
</body>
</html>
I don't know where you call the rotatedPosition function. This is only an idea, you also can store these values calling another function.
Related
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!
I'm working on a fish sprite animation. Currently when I add a piece of food then the sprite animation will move forward to eat it. But I am not able to make it swim smoothly toward the food. Sometimes the sprite animation will move up and down till it reach the food.
Here is how the sprite animation is moving towards the food:
Fish.prototype.chaseFood = function(index) {
if (this.xPos > foodArray[index].x + foodWidth) {
this.speedX = -1 * Math.abs(this.speedX);
} else if (this.xPos < foodArray[index].x) {
this.speedX = Math.abs(this.speedX);
}
if (this.yPos > foodArray[index].y + foodHeight) {
this.speedY = -1 * Math.abs(this.speedY);
} else if (this.yPos < foodArray[index].y) {
this.speedY = Math.abs(this.speedY);
}
};
Is there anyway to make it swim more smoothly towards the food and not moving up and down towards it.
I'd calculate angle between the fish and the food and make the fish move towards that angle.
Here are some helper functions to get you going:
function distanceBetweenPoints(a, b)
{
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
function angleBetweenPoints(a, b)
{
return Math.atan2(b.y-a.y,b.x-a.x)*180/Math.PI;
}
Usage:
var angle = angleBetweenPoints({ x: fish.x, y: fish.y }, { x: food.x, y: food.y });
Then you can do something like:
fish.x += Math.sin(angle * Math.PI / 180) * 10;
fish.y += Math.cos(angle * Math.PI / 180) * 10;
I think you want your sprite to move in a direct line from one set of coordinates (it's location) to another set of coordinates (the food's location).
So if your if your x coordinates are a difference of 6 pixels and your y coordinates are a difference of 4, we want to keep the ratio of 6 to 4 when we increase our x and y offset for our happy little fish.
In this case, we could change the program to move x by 1 pixel at a time and then move y by 4/6 pixel at a time and thus go on a path straight towards the goal. If we were to increase the y by a full pixel, it would arrive directly below the target and then go straight up. This would be an indirect path and less realistic looking.
Actually I think it would arrive to the left of the goal and then go directly right in the old version if you use the 1 to 1 ratio, but I think you knew what I meant.
I'll try to adapt an example I have to your code:
int fishSpeed = 2;
float xOffset = fishSpeed;
float yOffset = fishSpeed;
float xDis = abs(this.xPos-(foodArray[index].x + foodWidth));
float yDis = abs(this.yPos-(foodArray[index].y + foodWidth));
//each offset changes depending on how far it is from goal
xOffset = xOffset * (xDis / (xDis + yDis));
yOffset = yOffset * (yDis / (xDis + yDis));
if(this.xPos > foodArray[index].x) this.xPos+=xOffset;
if(this.yPos > foodArray[index].y) this.yPos+=yOffset;
if(this.xPos < foodArray[index].x) this.xPos-=xOffset;
if(this.yPos < foodArray[index].y) this.yPos-=yOffset;
Sorry I couldn't make it work using your example. I don't know if it will help, but here is a complete .htm file that has a bunny wabbit that is controlled by the mouse and one that is chased directly by another wabbit. The wabbit will go directly for its goal. The file needs processing.js in the same directory.
<!DOCTYPE html>
<html>
<body bgcolor="lightblue" style="margin:0;">
<center>
<script src="processing.js"></script>
<script type="application/processing">
void setup(){
size(screen.width*.9,screen.height*.9);
blueWabbit = new wabbit(600,600,105);
pinkWabbit = new wabbit(100,100,100);
blueWabbit.blue = 255;
blueWabbit.red = 128;
blueWabbit.green = 128;
wabbitSpeed = 5;
}
void draw() {
float xOffset = wabbitSpeed;
float yOffset = wabbitSpeed;
float xDis = abs(pinkWabbit.xpos-blueWabbit.xpos);
float yDis = abs(pinkWabbit.ypos-blueWabbit.ypos);
xOffset = xOffset * (xDis / (xDis + yDis));
yOffset = yOffset * (yDis / (xDis + yDis));
if(pinkWabbit.xpos > blueWabbit.xpos) blueWabbit.xpos+=xOffset;
if(pinkWabbit.ypos > blueWabbit.ypos) blueWabbit.ypos+=yOffset;
if(pinkWabbit.xpos < blueWabbit.xpos) blueWabbit.xpos-=xOffset;
if(pinkWabbit.ypos < blueWabbit.ypos) blueWabbit.ypos-=yOffset;
if (xDis+yDis<wabbitSpeed){
for(int a =0;a<20; a++)babyWabbit();
}
else background(0,0,0,0);
pinkWabbit.show();
blueWabbit.show();
pinkWabbit.xpos = mouseX;
pinkWabbit.ypos = mouseY;
/*
fill(0);
text("blue x = "+(int)blueWabbit.xpos,10,10);
text("blue y = "+(int)blueWabbit.ypos,10,20);
text("pink x = "+pinkWabbit.xpos,10,30);
text("pink y = "+pinkWabbit.ypos,10,40);
text("xOffset = "+xOffset,10,50);
text("yOffset = "+yOffset,10,60);
text("xDis = "+(int)xDis,10,70);
text("yDis = "+(int)yDis,10,80);
*/
}
class wabbit {
//declare the properties that will be used as variables for the object
float xpos, ypos, diameter;
int red, blue, green;
//define the parameters for the creation of a new class
wabbit (float x, float y, float wSize) {
xpos = x;
ypos = y;
diameter = wSize;
//radius = .5 * diameter;
//make it pink if user did not define colors
if (!(0>red>256)) red = 255;
if (!(0>green>256))green = 200;
if (!(0>blue>256)) blue = 200;
}
void show() {
noStroke();
for(a = diameter; a > 0; a-=5){
fill(red-a,green-a,blue-a);
//belly and head
ellipse(xpos, ypos, a, a);
ellipse(xpos, ypos-diameter*.7, a*.7,a*.7);
//feets
ellipse(xpos-.2*diameter, ypos+diameter*.4, a*.4,a*.4);
ellipse(xpos+.2*diameter, ypos+diameter*.4, a*.4,a*.4);
//ears
ellipse(xpos-.2*diameter, ypos-diameter, a*.2,a*.8);
ellipse(xpos+.2*diameter, ypos-diameter, a*.2,a*.8);
}
}
}
void babyWabbit(){
noStroke();
var red=random(1,255);
var green=random(1,255);
var blue=random(1,255);
var xpos=random(1,width);
var ypos=random(1,height);
var diameter = random(20,80);
for(var a = diameter; a > 0; a-=5){
fill(red-a,green-a,blue-a);
//belly and head
ellipse(xpos, ypos, a, a);
ellipse(xpos, ypos-diameter*.7, a*.7,a*.7);
//feets
ellipse(xpos-.2*diameter, ypos+diameter*.4, a*.4,a*.4);
ellipse(xpos+.2*diameter, ypos+diameter*.4, a*.4,a*.4);
//ears
ellipse(xpos-.2*diameter, ypos-diameter, a*.2,a*.8);
ellipse(xpos+.2*diameter, ypos-diameter, a*.2,a*.8);
}
}
</script><canvas></canvas>
</body>
</html>
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.
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.
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>