3

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:

enter image description here

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!

Spoeken
  • 2,549
  • 2
  • 28
  • 40
  • To clarify from your illustration above, are you trying to calculate triangle corner points A,C and what would be D? – markE Aug 08 '13 at 12:39
  • Oh, no. Not at all. The points are given. What I'm trying to do is calculate the skew angle and width of the rectangle to match up with the triangle. Or, another way to say it is that I want to match yp all the corners, they way illustrated. If you take a look here http://jsfiddle.net/p7g7Y/13/ You'll see that I haevent managed that yet. – Spoeken Aug 08 '13 at 13:11
  • Please ask more if this doesn't clarify it! – Spoeken Aug 08 '13 at 13:27

2 Answers2

1

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.

Community
  • 1
  • 1
6502
  • 112,025
  • 15
  • 165
  • 265
  • Now were on to something! But what is d, e, f? The deltas like in your link? In that case I need to find d in my rect right? – Spoeken Aug 11 '13 at 12:53
  • `a`, `b`, and `c` are the three linear coefficients needed for computing the transformed `X` from original `X` and `Y`. `d`, `e` and `f` are the coefficients needed to compute transformed `Y`. – 6502 Aug 11 '13 at 13:35
  • Ok, sorry. Got confused since I already use `a`, `b` and `c` in my code for x and y objects. But where will I get `a`, `b`, `c`, `d`, `e` and `f` from? – Spoeken Aug 12 '13 at 08:15
  • @MathiasMadsenStav: you need to solve the linear systems to find `a`...`f`. I've added an explicit solution using Cramer's rule and Sarrus' rule for determinants. – 6502 Aug 12 '13 at 09:03
  • I can't see how I can solve a, when I need `x_0` to find `a`, and `a` to find `x_0`. To me it looks like I have 6 known and 12 unknown. What am I not getting here? – Spoeken Aug 12 '13 at 10:09
  • Wait a minute. Is `x_0 = x2`? And `x_2 = x0`? – Spoeken Aug 12 '13 at 10:28
  • I've updated the code to match what I understood from your answer. It doesnt work, but maybe you can spot what I got wrong here. http://jsfiddle.net/p7g7Y/15/ – Spoeken Aug 12 '13 at 11:30
  • `(x0, y0)`, `(x1, y1)` and `(x2, y2)` are three points that you want mapped to `(x_0, y_0)`, `(x_1, y_1)` and `(x_2, y_2)`, respectively. Given those you can compute `a`, `b`, ... `f` by solving a linear system of 6 equations with 6 unknowns (a...f). This system is indeed not a general 6x6 but is composed of two 3x3 independent systems (one with a,b,c as unknown and another with d,e,f). Moreover the delta is the same for the two systems. – 6502 Aug 12 '13 at 12:17
  • So where do I then get `x0` from and where do I get `x_0` from? What do you mean by mapped to, is `x0 = x_0`? Or are you saying that we are going to move `x_0` to `x0`, hence `x_0 = topLeftCornerOfTexture`. I have given the triangle function three objects containing 2 floats each (x and y), 6 floats all in all. Each of these objects represent one corner in the triangle. Can I from these points find `x_0 ... y_2` and if so how? To me, it looks like you find `x_0` by saying `var x_0 = a*x0 + b*y0 + c;` but that doesn't make sense since `a` depends on `x_0`. – Spoeken Aug 12 '13 at 12:56
  • 1
    Suppose you want to find a,b,c,d,e,f so that (10,20) ends up in (123,456), (30,70) ends up in (567,212) and (51,83) ends up in (99,81). Then (x0, y0) is (10, 20) and (x_0, y_0) is (123, 456)... All these x0,y0,x1,y1,x2,y2,x_0,y_0,x_1,y_1,x_2,y_2 are known numbers. You need to compute a,b,c,d,e and f so that a*x0+b*y0+c = x_0, d*x0+e*y0+f=y_0 and so on. – 6502 Aug 12 '13 at 13:21
  • That explains a whole lot :) I don't really understand all the math and how it works, but it definitely works. Check out the result here: http://jsfiddle.net/p7g7Y/18/ – Spoeken Aug 12 '13 at 13:43
1

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.

enter image description here

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>
markE
  • 102,905
  • 11
  • 164
  • 176
  • Nice! This is what I initially tried to do. I check out how this works as well, and see which is most efficient. – Spoeken Aug 13 '13 at 10:02