Entity look at for cameras and objects - math

I'm currently extending my 3D-engine for a better entity system which includes cameras. This allows me to put cameras into parent entities which may also be in another entity ( and so on... ).
--Entity
----Entity
------Camera
Now i want to set the cameras look direction, i'm doing this with the following method which is also used to set the look at of a entity:
public void LookAt(Vector3 target, Vector3 up)
{
Matrix4x4 oldValue = _Matrix;
_Matrix = Matrix4x4.LookAt(_Position, target, up) * Matrix4x4.Scale(_Scale);
Vector3 p, s;
Quaternion r;
Quaternion oldRotation = _Rotation;
_Matrix.Decompose(out p, out s, out r);
_Rotation = r;
// Update dependency properties
ForceUpdate(RotationDeclaration, _Rotation, oldRotation);
ForceUpdate(MatrixDeclaration, _Matrix, oldValue);
}
But the code is only working for cameras and not for other entities, when using this method for other entities the object is rotating at it's position ( The entity is at a root node, so it has no parent ). The matrix's look at method looks like this:
public static Matrix4x4 LookAt(Vector3 position, Vector3 target, Vector3 up)
{
// Calculate and normalize forward vector
Vector3 forward = position - target;
forward.Normalize();
// Calculate and normalie side vector ( side = forward x up )
Vector3 side = Vector3.Cross(up, forward);
side.Normalize();
// Recompute up as: up = side x forward
up = Vector3.Cross(forward, side);
up.Normalize();
//------------------
Matrix4x4 result = new Matrix4x4(false)
{
M11 = side.X,
M21 = side.Y,
M31 = side.Z,
M41 = 0,
M12 = up.X,
M22 = up.Y,
M32 = up.Z,
M42 = 0,
M13 = forward.X,
M23 = forward.Y,
M33 = forward.Z,
M43 = 0,
M14 = 0,
M24 = 0,
M34 = 0,
M44 = 1
};
result.Multiply(Matrix4x4.Translation(-position.X, -position.Y, -position.Z));
return result;
}
The decompose method also returns the wrong value for the position variable p. So why is the camera working and the entity not?

I had a similar problem a while ago.The problem is that camera transformations are different from others as those are negated.What I did was first to transform camera eye, center and up into world space (transforming those vectors by its parent model matrix )and then calculating the lookAt() .That is how it worked for me.

Related

Find where line-segments intersect with a box

I am trying to figure out where a bunch of line-segments clip into a window around them. I saw the Liang–Barsky algorithm, but that seems to assume the segments already clip the edges of the window, which these do not.
Say I have a window from (0,0) to (26,16), and the following segments:
(7,6) - (16,3)
(10,6) - (19,6)
(13,10) - (21,3)
(16,12) - (19,14)
Illustration:
I imagine I need to extend the segments to a certain X or Y point, till they hit the edge of the window, but I don't know how.
How would I find the points where these segments (converted to lines?) clip into the edge of the window? I will be implementing this in C#, but this is pretty language-agnostic.
If you have two line segments P and Q with points
P0 - P1
Q0 - Q1
The line equations are
P = P0 + t(P1 - P0)
Q = Q0 + r(Q1 - Q0)
then to find out where they intersect after extension you need to solve the following equation for t and r
P0 + t(P1 - P0) = Q0 + r(Q1 - Q0)
The following code can do this. ( Extracted from my own code base )
public static (double t, double r )? SolveIntersect(this Segment2D P, Segment2D Q)
{
// a-d are the entries of a 2x2 matrix
var a = P.P1.X - P.P0.X;
var b = -Q.P1.X + Q.P0.X;
var c = P.P1.Y - P.P0.Y;
var d = -Q.P1.Y + Q.P0.Y;
var det = a*d - b*c;
if (Math.Abs( det ) < Utility.ZERO_TOLERANCE)
return null;
var x = Q.P0.X - P.P0.X;
var y = Q.P0.Y - P.P0.Y;
var t = 1/det*(d*x - b*y);
var r = 1/det*(-c*x + a*y);
return (t, r);
}
If null is returned from the function then it means the lines are parallel and cannot intersect. If a result is returned then you can do.
var result = SolveIntersect( P, Q );
if (result != null)
{
var ( t, r) = result.Value;
var p = P.P0 + t * (P.P1 - P.P0);
var q = Q.P0 + t * (Q.P1 - Q.P0);
// p and q are the same point of course
}
The extended line segments will generally intersect more than one box edge but only one of those intersections will be inside the box. You can check this easily.
bool IsInBox(Point corner0, Point corner1, Point test) =>
(test.X > corner0.X && test.X < corner1.X && test.Y > corner0.Y && test.Y < corner1.Y ;
That should give you all you need to extend you lines to the edge of your box.
I managed to figure this out.
I can extend my lines to the edge of the box by first finding the equations of my lines, then solving for the X and Y of each of the sides to get their corresponding point. This requires passing the max and min Y and the max and min X into the following functions, returning 4 values. If the point is outside the bounds of the box, it can be ignored.
My code is in C#, and is making extension methods for EMGU's LineSegment2D. This is a .NET wrapper for OpenCv.
My Code:
public static float GetYIntersection(this LineSegment2D line, float x)
{
Point p1 = line.P1;
Point p2 = line.P2;
float dx = p2.X - p1.X;
if(dx == 0)
{
return float.NaN;
}
float m = (p2.Y - p1.Y) / dx; //Slope
float b = p1.Y - (m * p1.X); //Y-Intercept
return m * x + b;
}
public static float GetXIntersection(this LineSegment2D line, float y)
{
Point p1 = line.P1;
Point p2 = line.P2;
float dx = p2.X - p1.X;
if (dx == 0)
{
return float.NaN;
}
float m = (p2.Y - p1.Y) / dx; //Slope
float b = p1.Y - (m * p1.X); //Y-Intercept
return (y - b) / m;
}
I can then take these points, check if they are in the bounds of the box, discard the ones that are not, remove duplicate points (line goes directly into corner). This will leave me with one x and one y value, which I can then pair to the corresponding min or max Y or X values I passed into the functions to make 2 points. I can then make my new segment with the two points.
Wiki description of Liang-Barsky algorithm is not bad, but code is flaw.
Note: this algorithm intended to throw out lines without intersection as soon as possible. If most of lines intersect the rectangle, then approach from your answer might be rather effective, otherwise L-B algorithm wins.
This page describes approach in details and contains concise effective code:
// Liang-Barsky function by Daniel White # http://www.skytopia.com/project/articles/compsci/clipping.html
// This function inputs 8 numbers, and outputs 4 new numbers (plus a boolean value to say whether the clipped line is drawn at all).
//
bool LiangBarsky (double edgeLeft, double edgeRight, double edgeBottom, double edgeTop, // Define the x/y clipping values for the border.
double x0src, double y0src, double x1src, double y1src, // Define the start and end points of the line.
double &x0clip, double &y0clip, double &x1clip, double &y1clip) // The output values, so declare these outside.
{
double t0 = 0.0; double t1 = 1.0;
double xdelta = x1src-x0src;
double ydelta = y1src-y0src;
double p,q,r;
for(int edge=0; edge<4; edge++) { // Traverse through left, right, bottom, top edges.
if (edge==0) { p = -xdelta; q = -(edgeLeft-x0src); }
if (edge==1) { p = xdelta; q = (edgeRight-x0src); }
if (edge==2) { p = -ydelta; q = -(edgeBottom-y0src);}
if (edge==3) { p = ydelta; q = (edgeTop-y0src); }
if(p==0 && q<0) return false; // Don't draw line at all. (parallel line outside)
r = q/p;
if(p<0) {
if(r>t1) return false; // Don't draw line at all.
else if(r>t0) t0=r; // Line is clipped!
} else if(p>0) {
if(r<t0) return false; // Don't draw line at all.
else if(r<t1) t1=r; // Line is clipped!
}
}
x0clip = x0src + t0*xdelta;
y0clip = y0src + t0*ydelta;
x1clip = x0src + t1*xdelta;
y1clip = y0src + t1*ydelta;
return true; // (clipped) line is drawn
}

Projection of a vector in processing using jama

I want to project A vector onto vector a and vector c, in Processing.
In my sketch vector a is red and c is blue, I wanted c to be perpendicular to b but this is where i'm having alot of trouble. I'm using the JAMA library to try and make this easier. Any help with this is much appreciated as I have been stumped for about a week now.
float X=200; // Origin : Note we have now centred the origin in the
X-direction float Y=350; float ax=150; // Vector a resolved into
components float ay=-50; float bx=0; // Vector b resolved into
components float by=-150; float cx=150; float cy=200;
Matrix a; Matrix b; Matrix c;
void setup() {
size(400,400); // Create a drawing window
strokeWeight(3); // Make pen 3 pixels wide for all lines
double [][] anums = {{ax},
{ay}};
double [][] bnums = {{bx},
{by}};
double [][] cnums = {{-cy},
{cx}};
a = new Matrix(anums);
b = new Matrix(bnums);
c = new Matrix(cnums); }
void draw() {
background(255); // Clear screen
// Evaluate equation (1.5)
// STEP1: Insert code here that computes a_unit (i.e. the unit vector in the
// direction of a
double length = a.norm2();
Matrix a_unit= a.times(1/length);
// STEP2: Insert code here to compute the dot product of b and a_unit
Matrix a_unit_T = a_unit.transpose();
Matrix projection = a_unit_T.times(b);
double lp = projection.get(0,0);
// STEP3 Insert code here to compute the vector p using equation 1.5 above Matrix p = a_unit.times(lp);
float px = (float)p.get(0,0);
float py = (float)p.get(1,0);
float ax = (float)a.get(0,0);
float ay = (float)a.get(1,0);
float bx = (float)b.get(0,0);
float by = (float)b.get(1,0);
float cx = (float)c.get(0,0);
float cy = (float)c.get(1,0);
// Draw the projection of b onto a
stroke(0,0,0); // Use a black pen
ellipse(X+px,Y+py,10,10); // point where b projects onto a
line(X+px,Y+py,X+bx,Y+by); // line from a to point of projection on b
stroke(255,0,0); // Make pen red
arrow(X,Y,X+ax,Y+ay); // Draw vector a starting at (X,Y)
//stroke(0,0,255);
//arrow(X,Y,X-ax,Y+ay);
stroke(0,255,0); // Make pen green
arrow(X,Y,X+bx,Y+by); // Draw vector b starting at (X,Y)
// STEP 4. Insert code here to add a new vector at 90 degrees to the vector a
stroke(0,0,255);
arrow(X,Y,X+cx,Y+cy);
// STEP 5. Insert code here to compute and draw the projection of b onto c
double length1 = c.norm2();
Matrix c_unit= c.times(1/length1);
// STEP2: Insert code here to compute the dot product of b and a_unit
Matrix c_unit_T = c_unit.transpose();
Matrix projection1 = c_unit_T.times(b);
double lp1 = projection.get(0,0);
// STEP3 Insert code here to compute the vector p using equation 1.5 above
Matrix r = c_unit.times(lp1);
float rx = (float)r.get(0,0);
float ry = (float)r.get(1,0);
stroke(0,0,0); // Use a black pen
ellipse(X+rx,Y+ry,10,10); // point where b projects onto a
line(X+rx,Y+ry,X+bx,Y+by); // line from a to point of projection on b
if (mouseButton == RIGHT)
{
a.set(0,0,(double)mouseX-X);
a.set(1,0,(double)mouseY-Y);
}
if (mouseButton == LEFT)
{
b.set(0,0,(double)mouseX-X);
b.set(1,0,(double)mouseY-Y);
} } // Draw an arrow from (x1,y1) to (x2,y2) void arrow(float x1, float y1, float x2, float y2) { line(x1, y1, x2, y2);
pushMatrix(); translate(x2, y2); float a = atan2(x1-x2, y2-y1);
rotate(a); line(0, 0, -8, -8); line(0, 0, 8, -8); popMatrix(); }
Here is the code mate,
float X=200; // Origin : Note we have now centred the origin in the X-direction
float Y=350;
float ax=300; // Vector a resolved into components
float ay=-100;
float bx=0; // Vector b resolved into components
float by=-300;
Matrix a;
Matrix b;
void setup()
{
size(400,400); // Create a drawing window
strokeWeight(3); // Make pen 3 pixels wide for all lines
double [][] anums = {{ax},
{ay}};
double [][] bnums = {{bx},
{by}};
a = new Matrix(anums);
b = new Matrix(bnums);
}
void draw()
{
background(255); // Clear screen
// Evaluate equation (1.5)
// STEP1: Insert code here that computes a_unit (i.e. the unit vector in the
// direction of a
double length = a.norm2();
Matrix a_unit = a.times(1/length);
// STEP2: Insert code here to compute the dot product of b and a_unit
Matrix a_unit_T = a_unit.transpose();
Matrix projection = a_unit_T.times(b);
double lp = projection.get(0,0);
// STEP3: Insert code here to compute the vector p using equation 1.5 above
Matrix p = a_unit.times(lp);
float px = (float)p.get(0,0);
float py = (float)p.get(1,0);
float ax = (float)a.get(0,0);
float ay = (float)a.get(1,0);
float bx = (float)b.get(0,0);
float by = (float)b.get(1,0);
// Draw the projection of b onto a
stroke(0,0,0); // Use a black pen
ellipse(X+px,Y+py,10,10); // point where b projects onto a
line(X+px,Y+py,X+bx,Y+by); // line from a to point of projection on b
stroke(255,0,0); // Make pen red
arrow(X,Y,X+ax,Y+ay); // Draw vector a starting at (X,Y)
stroke(0,255,0); // Make pen green
arrow(X,Y,X+bx,Y+by); // Draw vector b starting at (X,Y)
// STEP 4. Insert code here to add a new vector at 90 degrees to the vector a
double [][] cnums = {{ay},
{-ax}};
Matrix c = new Matrix(cnums);
float cx = (float)c.get(0,0);
float cy = (float)c.get(1,0);
stroke(0,0,255);
arrow(X,Y,X+cx,Y+cy);
// STEP 5. Insert code here to compute and draw the projection of b onto c
double length1 = c.norm2();
Matrix c_unit= c.times(1/length1);
Matrix c_unit_T = c_unit.transpose();
Matrix projection1 = c_unit_T.times(b);
double lp1 = projection1.get(0,0);
Matrix r = c_unit.times(lp1);
float rx = (float)r.get(0,0);
float ry = (float)r.get(1,0);
stroke(0,0,0); // Use a black pen
ellipse(X+rx,Y+ry,10,10); // point where b projects onto a
line(X+rx,Y+ry,X+bx,Y+by); // line from a to point of projection on b
if (mouseButton == RIGHT)
{
a.set(0,0,(double)mouseX-X);
a.set(1,0,(double)mouseY-Y);
}
if (mouseButton == LEFT)
{
b.set(0,0,(double)mouseX-X);
b.set(1,0,(double)mouseY-Y);
}
}
// Draw an arrow from (x1,y1) to (x2,y2)
void arrow(float x1, float y1, float x2, float y2)
{
line(x1, y1, x2, y2);
pushMatrix();
translate(x2, y2);
float a = atan2(x1-x2, y2-y1);
rotate(a);
line(0, 0, -8, -8);
line(0, 0, 8, -8);
popMatrix();
}

how to project curve to a planar in vtk

I have used the vtkDijkstraGraphGeodesicPath class(example: http://www.cmake.org/Wiki/VTK/Examples/Cxx/PolyData/DijkstraGraphGeodesicPath) to find the shortest path between two points on a mesh, and my next step is to project the path(curve) to a planar. Is there a class or function in vtk to project curve to planar?
And the other way is to sample the path(curve) and then project the sampled points to the planar, so how to sample the curve and get sampled points? Thank you in advance!‍
I never found a method doing the projection of 3D mesh, but I had to use it, and I selected texturizations methods allowing to project a mesh (to texzturize) on a Plane/Cylinder/Sphere.
The main method used in this case is vtkTextureMapToPlane.
// your mesh
vtkSmartPointer<vtkPolyData> mainMesh = myFilter->GetOutput ();
// extract your path from the poly data
// retrieve selected ids from dijkstra algo
vtkSmartPointer<vtkIdList> idList = dijkstra->GetIdList ();
vtkSmartPointer<vtkIdTypeArray> ids = convertToIdTypeArr (idList); // custom method to convert id array
// Once the ID selections is done, the extraction is invoked
vtkSmartPointer<vtkSelectionNode> selectionNode = vtkSelectionNode::New ();
selectionNode->SetFieldType (vtkSelectionNode::POINT);
selectionNode->SetContentType (vtkSelectionNode::INDICES);
selectionNode->SetSelectionList (ids);
vtkSmartPointer<vtkSelection> selection = vtkSelection::New ();
selection->AddNode (selectionNode);
vtkSmartPointer<vtkExtractSelection> extract = vtkExtractSelection::New ();
extract->SetInputData (0, pl);
extract->SetInputData (1, selection);
// convert result to polydata
vtkSmartPointer<vtkGeometryFilter> geoFilter = vtkGeometryFilter::New ();
geoFilter->SetInputConnection (extract->GetOutputPort());
geoFilter->Update();
vtkSmartPointer<vtkPolyData> selected = geoFilter->GetOutput();
You have a vtkpolyData with vertices from the path. You need to create the plane and project
// plane is sized with 800x600, on y-z directions
double orig[3] = {0, 0, 0};
double pt1[3] = {0, 600, 0};
double pt2[3] = {0, 0, 800};
// create TextureMapToPlan instance
vtkSmartPointer<vtkTextureMapToPlane> planeMapper = vtkTextureMapToPlane::New ();
planeMapper->SetOrigin(orig);
planeMapper->SetPoint1(pt1);
planeMapper->SetPoint2(pt2);
planeMapper->SetInputData (selected);
planeMapper->Update (); // project
vtkSmartPointer<vtkPolyData> d = planeMapper->GetPolyDataOutput(); // retrieve result
As this algorithm is used for texturization, you need to retrieve Texture coords, and convert them into the plane coordinates. (Text coords are defined in [0, 1] ratio of the height and width)
vtkSmartPointer<vtkDataArray> textCoord = d->GetPointData()->GetTCoords ();
vtkSmartPointer <vtkPoints> textPoints = vtkPoints::New ();
for (int i = 0; i < textCoord->GetNumberOfTuples (); ++i)
{
textPoints->InsertNextPoint (textCoord->GetTuple2(i)[0] * 800,
textCoord->GetTuple2(i)[1] * 600, 0);
}
textPoints got here all coordinates in 2 dimension of the projection of your path on the plane. /!\ This coords depends on your plane coordinates.

Quaternion based rotation and pivot position

I can't figure out how to perform matrix rotation using Quaternion while taking into account pivot position in OpenGL.What I am currently getting is rotation of the object around some point in the space and not a local pivot which is what I want.
Here is the code [Using Java]
Quaternion rotation method:
public void rotateTo3(float xr, float yr, float zr) {
_rotation.x = xr;
_rotation.y = yr;
_rotation.z = zr;
Quaternion xrotQ = Glm.angleAxis((xr), Vec3.X_AXIS);
Quaternion yrotQ = Glm.angleAxis((yr), Vec3.Y_AXIS);
Quaternion zrotQ = Glm.angleAxis((zr), Vec3.Z_AXIS);
xrotQ = Glm.normalize(xrotQ);
yrotQ = Glm.normalize(yrotQ);
zrotQ = Glm.normalize(zrotQ);
Quaternion acumQuat;
acumQuat = Quaternion.mul(xrotQ, yrotQ);
acumQuat = Quaternion.mul(acumQuat, zrotQ);
Mat4 rotMat = Glm.matCast(acumQuat);
_model = new Mat4(1);
scaleTo(_scaleX, _scaleY, _scaleZ);
_model = Glm.translate(_model, new Vec3(_pivot.x, _pivot.y, 0));
_model =rotMat.mul(_model);//_model.mul(rotMat); //rotMat.mul(_model);
_model = Glm.translate(_model, new Vec3(-_pivot.x, -_pivot.y, 0));
translateTo(_x, _y, _z);
notifyTranformChange();
}
Model matrix scale method:
public void scaleTo(float x, float y, float z) {
_model.set(0, x);
_model.set(5, y);
_model.set(10, z);
_scaleX = x;
_scaleY = y;
_scaleZ = z;
notifyTranformChange();
}
Translate method:
public void translateTo(float x, float y, float z) {
_x = x - _pivot.x;
_y = y - _pivot.y;
_z = z;
_position.x = _x;
_position.y = _y;
_position.z = _z;
_model.set(12, _x);
_model.set(13, _y);
_model.set(14, _z);
notifyTranformChange();
}
But this method in which I don't use Quaternion works fine:
public void rotate(Vec3 axis, float angleDegr) {
_rotation.add(axis.scale(angleDegr));
// change to GLM:
Mat4 backTr = new Mat4(1.0f);
backTr = Glm.translate(backTr, new Vec3(_pivot.x, _pivot.y, 0));
backTr = Glm.rotate(backTr, angleDegr, axis);
backTr = Glm.translate(backTr, new Vec3(-_pivot.x, -_pivot.y, 0));
_model =_model.mul(backTr);///backTr.mul(_model);
notifyTranformChange();
}
It seems to me you take into account the back and forth translation before and after the rotation already. Why that final call of translateTo?
Besides, when you rotate, a pure rotation is always meant around the origin. So if you want a rotation around your pivot point. I'd exptect to translate your pivot point to the origin, then rotate, then translate back to the pivot would be the right thing to do. Therefore, I'd expect your code to look like this:
_model = Glm.translate(_model, new Vec3(-_pivot.x, -_pivot.y, 0));
_model =rotMat.mul(_model);//_model.mul(rotMat); //rotMat.mul(_model);
_model = Glm.translate(_model, new Vec3(_pivot.x, _pivot.y, 0));
and without the call translateTo(_x, _y, _z);. Also, can you confirm that the rotation part already does what it supposed to? You can check this by comparing rotMat with Glm.rotate(new Mat4(1.0f), angleDegr, axis). They should be the same for the same rotation.
A quaternion describes only a rotation. As a result how do you want to rotate something around a pivot point with only a quaternion?
The minimum that you need is a R3 vector and a quaternion. With only one level of transformation you first roatate the object and then move it there.
If you want to create a matrix you first create the ration matrix and then add the translation unaltered.
If you want to just call glTranslate and glRotate (or glMultMatrix) you would first call glTranslate and then glRoatate.
Edit:
If you are not rendering and just want to know where each vertex is:
Vector3 newVertex = quat.transform(oldVertex) + translation;

correct glsl affine texture mapping

i'm trying to code correct 2D affine texture mapping in GLSL.
Explanation:
...NONE of this images is correct for my purposes. Right (labeled Correct) has perspective correction which i do not want. So this: Getting to know the Q texture coordinate solution (without further improvements) is not what I'm looking for.
I'd like to simply "stretch" texture inside quadrilateral, something like this:
but composed from two triangles. Any advice (GLSL) please?
This works well as long as you have a trapezoid, and its parallel edges are aligned with one of the local axes. I recommend playing around with my Unity package.
GLSL:
varying vec2 shiftedPosition, width_height;
#ifdef VERTEX
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
shiftedPosition = gl_MultiTexCoord0.xy; // left and bottom edges zeroed.
width_height = gl_MultiTexCoord1.xy;
}
#endif
#ifdef FRAGMENT
uniform sampler2D _MainTex;
void main() {
gl_FragColor = texture2D(_MainTex, shiftedPosition / width_height);
}
#endif
C#:
// Zero out the left and bottom edges,
// leaving a right trapezoid with two sides on the axes and a vertex at the origin.
var shiftedPositions = new Vector2[] {
Vector2.zero,
new Vector2(0, vertices[1].y - vertices[0].y),
new Vector2(vertices[2].x - vertices[1].x, vertices[2].y - vertices[3].y),
new Vector2(vertices[3].x - vertices[0].x, 0)
};
mesh.uv = shiftedPositions;
var widths_heights = new Vector2[4];
widths_heights[0].x = widths_heights[3].x = shiftedPositions[3].x;
widths_heights[1].x = widths_heights[2].x = shiftedPositions[2].x;
widths_heights[0].y = widths_heights[1].y = shiftedPositions[1].y;
widths_heights[2].y = widths_heights[3].y = shiftedPositions[2].y;
mesh.uv2 = widths_heights;
I recently managed to come up with a generic solution to this problem for any type of quadrilateral. The calculations and GLSL maybe of help. There's a working demo in java (that runs on Android), but is compact and readable and should be easily portable to unity or iOS: http://www.bitlush.com/posts/arbitrary-quadrilaterals-in-opengl-es-2-0
In case anyone's still interested, here's a C# implementation that takes a quad defined by the clockwise screen verts (x0,y0) (x1,y1) ... (x3,y3), an arbitrary pixel at (x,y) and calculates the u and v of that pixel. It was originally written to CPU-render an arbitrary quad to a texture, but it's easy enough to split the algorithm across CPU, Vertex and Pixel shaders; I've commented accordingly in the code.
float Ax, Bx, Cx, Dx, Ay, By, Cy, Dy, A, B, C;
//These are all uniforms for a given quad. Calculate on CPU.
Ax = (x3 - x0) - (x2 - x1);
Bx = (x0 - x1);
Cx = (x2 - x1);
Dx = x1;
Ay = (y3 - y0) - (y2 - y1);
By = (y0 - y1);
Cy = (y2 - y1);
Dy = y1;
float ByCx_plus_AyDx_minus_BxCy_minus_AxDy = (By * Cx) + (Ay * Dx) - (Bx * Cy) - (Ax * Dy);
float ByDx_minus_BxDy = (By * Dx) - (Bx * Dy);
A = (Ay*Cx)-(Ax*Cy);
//These must be calculated per-vertex, and passed through as interpolated values to the pixel-shader
B = (Ax * y) + ByCx_plus_AyDx_minus_BxCy_minus_AxDy - (Ay * x);
C = (Bx * y) + ByDx_minus_BxDy - (By * x);
//These must be calculated per-pixel using the interpolated B, C and x from the vertex shader along with some of the other uniforms.
u = ((-B) - Mathf.Sqrt((B*B-(4.0f*A*C))))/(A*2.0f);
v = (x - (u * Cx) - Dx)/((u*Ax)+Bx);
Tessellation solves this problem. Subdividing quad vertex adds hints to interpolate pixels.
Check out this link.
https://www.youtube.com/watch?v=8TleepxIORU&feature=youtu.be
I had similar question ( https://gamedev.stackexchange.com/questions/174857/mapping-a-texture-to-a-2d-quadrilateral/174871 ) , and at gamedev they suggested using imaginary Z coord, which I calculate using the following C code, which appears to be working in general case (not just trapezoids):
//usual euclidean distance
float distance(int ax, int ay, int bx, int by) {
int x = ax-bx;
int y = ay-by;
return sqrtf((float)(x*x + y*y));
}
void gfx_quad(gfx_t *dst //destination texture, we are rendering into
,gfx_t *src //source texture
,int *quad // quadrilateral vertices
)
{
int *v = quad; //quad vertices
float z = 20.0;
float top = distance(v[0],v[1],v[2],v[3]); //top
float bot = distance(v[4],v[5],v[6],v[7]); //bottom
float lft = distance(v[0],v[1],v[4],v[5]); //left
float rgt = distance(v[2],v[3],v[6],v[7]); //right
// By default all vertices lie on the screen plane
float az = 1.0;
float bz = 1.0;
float cz = 1.0;
float dz = 1.0;
// Move Z from screen, if based on distance ratios.
if (top<bot) {
az *= top/bot;
bz *= top/bot;
} else {
cz *= bot/top;
dz *= bot/top;
}
if (lft<rgt) {
az *= lft/rgt;
cz *= lft/rgt;
} else {
bz *= rgt/lft;
dz *= rgt/lft;
}
// draw our quad as two textured triangles
gfx_textured(dst, src
, v[0],v[1],az, v[2],v[3],bz, v[4],v[5],cz
, 0.0,0.0, 1.0,0.0, 0.0,1.0);
gfx_textured(dst, src
, v[2],v[3],bz, v[4],v[5],cz, v[6],v[7],dz
, 1.0,0.0, 0.0,1.0, 1.0,1.0);
}
I'm doing it in software to scale and rotate 2d sprites, and for OpenGL 3d app you will need to do it in pixel/fragment shader, unless you will be able to map these imaginary az,bz,cz,dz into your actual 3d space and use the usual pipeline. DMGregory gave exact code for OpenGL shaders: https://gamedev.stackexchange.com/questions/148082/how-can-i-fix-zig-zagging-uv-mapping-artifacts-on-a-generated-mesh-that-tapers
I came up with this issue as I was trying to implement a homography warping in OpenGL. Some of the solutions that I found relied on a notion of depth, but this was not feasible in my case since I am working on 2D coordinates.
I based my solution on this article, and it seems to work for all cases that I could try. I am leaving it here in case it is useful for someone else as I could not find something similar. The solution makes the following assumptions:
The vertex coordinates are the 4 points of a quad in Lower Right, Upper Right, Upper Left, Lower Left order.
The coordinates are given in OpenGL's reference system (range [-1, 1], with origin at bottom left corner).
std::vector<cv::Point2f> points;
// Convert points to homogeneous coordinates to simplify the problem.
Eigen::Vector3f p0(points[0].x, points[0].y, 1);
Eigen::Vector3f p1(points[1].x, points[1].y, 1);
Eigen::Vector3f p2(points[2].x, points[2].y, 1);
Eigen::Vector3f p3(points[3].x, points[3].y, 1);
// Compute the intersection point between the lines described by opposite vertices using cross products. Normalization is only required at the end.
// See https://leimao.github.io/blog/2D-Line-Mathematics-Homogeneous-Coordinates/ for a quick summary of this approach.
auto line1 = p2.cross(p0);
auto line2 = p3.cross(p1);
auto intersection = line1.cross(line2);
intersection = intersection / intersection(2);
// Compute distance to each point.
for (const auto &pt : points) {
auto distance = std::sqrt(std::pow(pt.x - intersection(0), 2) +
std::pow(pt.y - intersection(1), 2));
distances.push_back(distance);
}
// Assumes same order as above.
std::vector<cv::Point2f> texture_coords_unnormalized = {
{1.0f, 1.0f},
{1.0f, 0.0f},
{0.0f, 0.0f},
{0.0f, 1.0f}
};
std::vector<float> texture_coords;
for (int i = 0; i < texture_coords_unnormalized.size(); ++i) {
float u_i = texture_coords_unnormalized[i].x;
float v_i = texture_coords_unnormalized[i].y;
float d_i = distances.at(i);
float d_i_2 = distances.at((i + 2) % 4);
float scale = (d_i + d_i_2) / d_i_2;
texture_coords.push_back(u_i*scale);
texture_coords.push_back(v_i*scale);
texture_coords.push_back(scale);
}
Pass the texture coordinates to your shader (use vec3). Then:
gl_FragColor = vec4(texture2D(textureSampler, textureCoords.xy/textureCoords.z).rgb, 1.0);
thanks for answers, but after experimenting i found a solution.
two triangles on the left has uv (strq) according this and two triangles on the right are modifed version of this perspective correction.
Numbers and shader:
tri1 = [Vec2(-0.5, -1), Vec2(0.5, -1), Vec2(1, 1)]
tri2 = [Vec2(-0.5, -1), Vec2(1, 1), Vec2(-1, 1)]
d1 = length of top edge = 2
d2 = length of bottom edge = 1
tri1_uv = [Vec4(0, 0, 0, d2 / d1), Vec4(d2 / d1, 0, 0, d2 / d1), Vec4(1, 1, 0, 1)]
tri2_uv = [Vec4(0, 0, 0, d2 / d1), Vec4(1, 1, 0, 1), Vec4(0, 1, 0, 1)]
only right triangles are rendered using this glsl shader (on left is fixed pipeline):
void main()
{
gl_FragColor = texture2D(colormap, vec2(gl_TexCoord[0].x / glTexCoord[0].w, gl_TexCoord[0].y);
}
so.. only U is perspective and V is linear.

Resources