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>