A couple of days ago I started looking into efficiently drawing bezier curves, and I came across a this method developed by Charles Loop and Jim Blinn that seemed very interesting. How ever, after a lot of experimenting with their algorithm, I just can't seem to get it to be able to render the cubic curves. Quadratics are fine, no problem there.
The only resources I have found so far are as follows:
GPU Gems 3 Chapter 25
Curvy Blues
Resolution Independent Curve Rendering using Programmable Graphics Hardware
To get the testing up and running quickly, I'm doing this in XNA. Basically I'm passing texture coordinates with my vertices to the GPU, apply a perspective transform and use the formula mentioned in all the articles in a pixel shader to render the final result. How ever, the problem (I think) lays in how I calculate the texture coordinates. Check this code out:
public void Update()
{
float a1 = Vector3.Dot(p1, Vector3.Cross(p4, p3));
float a2 = Vector3.Dot(p2, Vector3.Cross(p1, p4));
float a3 = Vector3.Dot(p3, Vector3.Cross(p2, p2));
float d1 = a1 - 2 * a2 + 3 * a3;
float d2 = -a2 + 3 * a3;
float d3 = 3 * a3;
float discr = d1 * d1 * (3 * d2 * d2 - 4 * d1 * d3);
if (discr > 0)
{
Type = CurveTypes.Serpentine;
float ls = 3 * d2 - (float)Math.Sqrt(9 * d2 * d2 - 12 * d1 * d3);
float lt = 6 * d1;
float ms = 3 * d2 + (float)Math.Sqrt(9 * d2 * d2 - 12 * d1 * d3);
float mt = 6 * d1;
TexCoord1 = new Vector3(ls * ms, (float)Math.Pow(ls, 3), (float)Math.Pow(ms, 3));
TexCoord2 = new Vector3((3 * ls * ms - ls * mt - lt * ms) / 3, ls * ls * (ls - lt), ms * ms * (ms - mt));
TexCoord3 = new Vector3((lt * (mt - 2 * ms) + ls * (3 * ms - 2 * mt)) / 3, (float)Math.Pow(lt - ls, 2) * ls, (float)Math.Pow(mt - ms, 2) * ms);
TexCoord4 = new Vector3((lt - ls) * (mt - ms), -(float)Math.Pow(lt - ls, 3), -(float)Math.Pow(mt - ms, 3));
}
else if (discr == 0)
{
Type = CurveTypes.Cusp;
}
else if (discr < 0)
{
Type = CurveTypes.Loop;
}
}
Excuse the mess, it's just some testing code. p1...p4 are the control points in world space, and TexCoord1...TexCoord4 are the corresponding texture coordinates. This is a replication of what is said in the GPU Gems article.
There are a few problems here, first when calculating a3, we use p2 for both parameters, which of course always results in a (0,0,0) vector, and taking the dot product of that and p3 will always give us 0. That't pretty useless, so why would they mention that in the article?
This will of course make discr incorrect, and we won't even be able to determine what type of curve it is.
After fiddling around with that code for a while, I decided to try to do it exactly the why they did in in the Loop and Blinn paper. From that I get something like this:
public void Update()
{
Matrix m1 = new Matrix(
p4.X, p4.Y, 1, 0,
p3.X, p3.Y, 1, 0,
p2.X, p2.Y, 1, 0,
0, 0, 0, 1);
Matrix m2 = new Matrix(
p4.X, p4.Y, 1, 0,
p3.X, p3.Y, 1, 0,
p1.X, p1.Y, 1, 0,
0, 0, 0, 1);
Matrix m3 = new Matrix(
p4.X, p4.Y, 1, 0,
p2.X, p2.Y, 1, 0,
p1.X, p1.Y, 1, 0,
0, 0, 0, 1);
Matrix m4 = new Matrix(
p3.X, p3.Y, 1, 0,
p2.X, p2.Y, 1, 0,
p1.X, p1.Y, 1, 0,
0, 0, 0, 1);
float det1 = m1.Determinant();
float det2 = -m2.Determinant();
float det3 = m3.Determinant();
float det4 = -m4.Determinant();
float tet1 = det1 * det3 - det2 * det2;
float tet2 = det2 * det3 - det1 * det4;
float tet3 = det2 * det4 - det3 * det3;
float discr = 4 * tet1 * tet3 - tet2 * tet2;
if (discr > 0)
{
Type = CurveTypes.Serpentine;
float ls = 2 * det2;
float lt = det3 + (float)((1 / Math.Sqrt(3)) * Math.Sqrt(3 * det3 * det3 - 4 * det2 * det4));
float ms = 2 * det2;
float mt = det3 - (float)((1 / Math.Sqrt(3)) * Math.Sqrt(3 * det3 * det3 - 4 * det2 * det4));
TexCoord1 = new Vector3(lt * mt, (float)Math.Pow(lt, 3), (float)Math.Pow(mt, 3));
TexCoord2 = new Vector3(-ms * lt - ls * mt, -3 * ls * lt * lt, -3 * ms * mt * mt);
TexCoord3 = new Vector3(ls * ms, 3 * ls * ls * lt, 3 * ms * ms * mt);
TexCoord4 = new Vector3(0, -ls * ls * ls, -ms * ms * ms);
}
else if (discr == 0)
{
Type = CurveTypes.Cusp;
}
else if (discr < 0)
{
Type = CurveTypes.Loop;
}
}
Guess what, that didn't work either. How ever, discr seem to be at least a little more correct now. At least it has the right sign, and it is zero when the control points are arranged to form a cusp. I still get the same visual result though, except the curve disappears randomly for a while (the pixel shader formula is always greater than zero) and returns after I move the control point back to more of a square shape. Here is the pixel shader code by the way:
PixelToFrame PixelShader(VertexToPixel PSIn)
{
PixelToFrame Output = (PixelToFrame)0;
if(pow(PSIn.TexCoords.x, 3) - PSIn.TexCoords.y * PSIn.TexCoords.z > 0)
{
Output.Color = float4(0,0,0,0.1);
}
else
{
Output.Color = float4(0,1,0,1);
}
return Output;
}
That is about all useful information I can think of right now. Does anyone have any idea what's going on? Because I'm running out of them.
I was looking at the paper and your code, and it seams you're missing the multiplication to the M3 matrix.
Your p1, p2, p3 and p4 coordinates should be placed in a matrix and multiplied by the M3 matrix, before using it to calculate the determinants.
eg.
Matrix M3 = Matrix(
1, 0, 0, 0,
-3, 3, 0, 0,
3, -6, 3, 0,
-1, 3, -3, 1);
Matrix B = Matrix(
p1.X, p1.Y, 0, 1,
p2.X, p2.Y, 0, 1,
p3.X, p3.Y, 0, 1,
p4.X, p4.Y, 0, 1);
Matrix C = M3*B;
Then you use each row of the C matrix as the coordinates for the m1 to m4 matrices in your code. Where the first and second values of the row are the x,y coordinates and the last is the w coordinate.
Finally the matrix of texture coordinates needs to be mutiplied by the inverse of M3
eg.
Matrix invM3 = Matrix(
1, 0, 0, 0,
1, 0.3333333, 0, 0,
1, 0.6666667, 0.333333, 0,
1, 1, 1, 1);
Matrix F = Matrix(
TexCoord1,
TexCoord2,
TexCoord3,
TexCoord4);
Matrix result = invM3*F;
Each row of the resulting matrix corresponds to the texture coordinates needed for the shader.
I haven't implemented it myself yet, so cannot guarantee that that it will solve your problem. It simply is what I noticed to be missing from your implementation after reading the paper.
I hope this helps, if I'm wrong please tell me cause I will be trying this out soon.
Related
I am very new to OpenGL and am using the latest version of OpenTK with C#.
My camera class currently does the following,
public Matrix4 GetProjectionMatrix()
{
return Matrix4.CreatePerspectiveFieldOfView(_fov, AspectRatio, 0.01f, 100f);
}
public Matrix4 GetViewMatrix()
{
Vector3 lookAt = new Vector3(myObject.Pos.X, myObject.Pos.Y, myObject.Pos.Z);
return Matrix4.LookAt(Position, lookAt, _up);
}
I have a slightly weird use case, where my game window will be long, something like a 4:12 ratio, and it will present a long object. From my reading, online the best way to present this the way I want is to do a lense shift (Oblique Frustum).
I've seen articles online on how to do this, namely:
http://www.terathon.com/code/oblique.html
https://docs.unity3d.com/Manual/ObliqueFrustum.html
But I am having trouble translating this to OpenTk.
Was wondering if anyone on here has done something similar to this in OpenTK.
EDIT:
This kind of worked, but not quite what I was looking for :(
private float sgn(float a)
{
if (a > 0.0F) return (1.0F);
if (a < 0.0F) return (-1.0F);
return (0.0F);
}
public Matrix4 CreatePerspectiveFieldOfView(Matrix4 projectionMatrix)
{
Vector4 clipPlane = new Vector4(0.0f, 0.7f, 1.0f , 1.0f);
Vector4 q = new Vector4
{
X = (sgn(clipPlane.X) + projectionMatrix.M13) / projectionMatrix.M11,
Y = (sgn(clipPlane.Y) + projectionMatrix.M23) / projectionMatrix.M22,
Z = -1.0f,
W = (1.0F + projectionMatrix.M33) / projectionMatrix.M34
};
Vector4 c = clipPlane * (2.0F / Vector4.Dot(clipPlane, q));
projectionMatrix.M31 = c.X;
projectionMatrix.M32 = c.Y;
projectionMatrix.M33 = c.Z + 1.0f;
projectionMatrix.M34 = c.W;
return projectionMatrix;
}
EDIT 2:
Basically what I am looking to do, is bring the look at point closer to the edge of the frustum like so:
There are some obvious issues. OpenGL matrices are column major order. Hence, i is the column and j is the row for the Mij properties of Matrix4:
In the following columns are from the top to the bottom and rows are form the left to the right, because that is the representation of the fields of the matrix in memory and how a matrix "looks" in the debuger:
row1 row2 row3 row4 indices
column1 (M11, M12, M13, M14) ( 0, 1, 2, 3)
column2 (M21, M22, M23, M24) ( 4, 5, 6, 7)
column3 (M31, M32, M33, M34) ( 8, 9, 10, 11)
column4 (M41, M42, M43, M44) (12, 13, 14, 15)
Thus
q.x = (sgn(clipPlane.x) + matrix[8]) / matrix[0];
q.y = (sgn(clipPlane.y) + matrix[9]) / matrix[5];
q.z = -1.0F;
q.w = (1.0F + matrix[10]) / matrix[14];
has to be translated to:
Vector4 q = new Vector4
{
X = (sgn(clipPlane.X) + projectionMatrix.M31) / projectionMatrix.M11,
Y = (sgn(clipPlane.Y) + projectionMatrix.M32) / projectionMatrix.M22,
Z = -1.0f,
W = (1.0f + projectionMatrix.M33) / projectionMatrix.M43
};
and
```cpp
matrix[2] = c.x;
matrix[6] = c.y;
matrix[10] = c.z + 1.0F;
matrix[14] = c.w;
has to be
projectionMatrix.M13 = c.X;
projectionMatrix.M23 = c.Y;
projectionMatrix.M33 = c.Z + 1.0f;
projectionMatrix.M43 = c.W;
If you want an asymmetric perspective projection, then consider to to create the perojection matrix by Matrix4.CreatePerspectiveOffCenter.
public Matrix4 GetProjectionMatrix()
{
var offset_x = -0.0f;
var offset_y = -0.005f; // just for instance
var tan_fov_2 = Math.Tan(_fov / 2.0);
var near = 0.01f;
var far = 100.0f;
var left = -near * AspectRatio * tan_fov_2 + offset_x;
var right = near * AspectRatio * tan_fov_2 + offset_x;
var bottom = -near * tan_fov_2 + offset_y;
var top = near * tan_fov_2 + offset_y;
return Matrix4.CreatePerspectiveOffCenter(left, right, bottom, top, near, far);
}
My project's main goal is to project a player character from a 3D environment to a 2D screen. I already found some useful math online, but in the last four days I couldn't make it work. Everytime I move the character it appears in random places and/or goes off screen. Sometimes it follows horizontally, but usually never vertically.
My question is pretty simple: What am I doing wrong?
// A few numbers to play with:
// Case #1: myPos: 1104.031, 3505.031, -91.9875; myMousePos: 0, 180; myRotation: 153, 153, 25; playerPos: 1072, 3504, -91.9687 (Player Middle of Screen, Standing Behind Player)
// Case #2: myPos: 511.7656, 3549.25, -28.02344; myMousePos: 0, 347.5854; myRotation: 44, 2, 22; playerPos: 1632, 3232, -91.96875 (Player Middle of Screen, I stand higher and 1166 away)
// Case #3: myPos: 1105.523, 2898.336, -11.96875; myMousePos: 0, 58.67249; myRotation: 232, 184, 159; playerPos 1632, 3232, -91.96875 (Player Right, Lower Of Screen)
Vect3d viewAngles;
Vect3d vForward, vRight, vUpward;
float ScreenX, ScreenY;
float[] fov;
bool 3dWorldTo2dScreen(Vect3d playerpos, Vect3d mypos, PlayerData myself)
{
fov = new float[2];
viewAngles = Vect3d();
Vect3d vLocal, vTransForm;
vTransForm = new Vect3d();
vForward = new Vect3d();
vRight = new Vect3d();
vUpward = new Vect3d();
fov[0] = myself.MouseX; // Sky: -89, Ground: 89, Middle: 0
fov[1] = myself.MouseY; // 360 To 0
viewAngles.x = myself.Rotation.x;
viewAngles.y = myself.Rotation.y;
viewAngles.z = myself.Rotation.z;
int screenCenterX = 320; // 640
int screenCenterY = 240; // 480
AngleVectors();
vLocal = SubVectorDist(playerpos, mypos);
vTransForm.x = vLocal.dotproduct(vRight);
vTransForm.y = vLocal.dotproduct(vUpward);
vTransForm.z = vLocal.dotproduct(vForward);
if (vTransForm.z < 0.01)
return false;
ScreenX = screenCenterX + (screenCenterX / vTransForm.z * (1 / fov[0])) * vTransForm.x;
ScreenY = screenCenterY - (screenCenterY / vTransForm.z * (1 / fov[1])) * vTransForm.y;
return true;
}
Vect3d SubVectorDist(Vect3d playerFrom, Vect3d playerTo)
{
return new Vect3d(playerFrom.x - playerTo.x, playerFrom.y - playerTo.y, playerFrom.z - playerTo.z);
}
private void AngleVectors()
{
float angle;
float sr, sp, sy, cr, cp, cy,
cpi = (3.141f * 2 / 360);
angle = viewAngles.y * cpi;
sy = (float)Math.Sin(angle);
cy = (float)Math.Cos(angle);
angle = viewAngles.x * cpi;
sp = (float)Math.Sin(angle);
cp = (float)Math.Cos(angle);
angle = viewAngles.z * cpi;
sr = (float)Math.Sin(angle);
cr = (float)Math.Cos(angle);
vForward.x = cp * cy;
vForward.y = cp * sy;
vForward.z = -sp;
vRight.x = (-1 * sr * sp * cy + -1 * cr * -sy);
vRight.y = (-1 * sr * sp * sy + -1 * cr * cy);
vRight.z = -1 * sr * cp;
vUpward.x = (cr * sp * cy + -sr * -sy);
vUpward.y = (cr * sp * sy + -sr * cy);
vUpward.z = cr * cp;
}
To get the line of intersection between two rectangles in 3D, I converted them to planes, then get the line of intersection using cross product of their normals, then I try to get the line intersection with each line segment of the rectangle.
The problem is the line is parallel to three segments, and intersect with only one in NAN,NAN,NAN which is totally wrong. Can you advise me what's wrong in my code?
I use vector3 from this link http://www.koders.com/csharp/fidCA8558A72AF7D3E654FDAFA402A168B8BC23C22A.aspx
and created my plane class as following
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace referenceLineAlgorithm
{
struct Line
{
public Vector3 direction;
public Vector3 point;
}
struct lineSegment
{
public Vector3 firstPoint;
public Vector3 secondPoint;
}
class plane_test
{
public enum Line3DResult
{
Line3DResult_Parallel = 0,
Line3DResult_SkewNoCross = 1,
Line3DResult_SkewCross = 2
};
#region Fields
public Vector3 Normal;
public float D;
public Vector3[] cornersArray;
public Vector3 FirstPoint;
public Vector3 SecondPoint;
public Vector3 temp;
public Vector3 normalBeforeNormalization;
#endregion
#region constructors
public plane_test(Vector3 point0, Vector3 point1, Vector3 point2, Vector3 point3)
{
Vector3 edge1 = point1 - point0;
Vector3 edge2 = point2 - point0;
Normal = edge1.Cross(edge2);
normalBeforeNormalization = Normal;
Normal.Normalize();
D = -Normal.Dot(point0);
///// Set the Rectangle corners
cornersArray = new Vector3[] { point0, point1, point2, point3 };
}
#endregion
#region Methods
/// <summary>
/// This is a pseudodistance. The sign of the return value is
/// positive if the point is on the positive side of the plane,
/// negative if the point is on the negative side, and zero if the
/// point is on the plane.
/// The absolute value of the return value is the true distance only
/// when the plane normal is a unit length vector.
/// </summary>
/// <param name="point"></param>
/// <returns></returns>
public float GetDistance(Vector3 point)
{
return Normal.Dot(point) + D;
}
public void Intersection(plane_test SecondOne)
{
///////////////////////////// Get the parallel to the line of interrsection (Direction )
Vector3 LineDirection = Normal.Cross(SecondOne.Normal);
float d1 = this.GetDistance(LineDirection);
float d2 = SecondOne.GetDistance(LineDirection);
temp = (LineDirection - (this.Normal * d1) - (SecondOne.Normal * d2));
temp.x = Math.Abs((float)Math.Round((decimal)FirstPoint.x, 2));
temp.y = Math.Abs((float)Math.Round((decimal)FirstPoint.y, 2));
Line line;
line.direction = LineDirection;
line.point = temp;
////////// Line segments
lineSegment AB, BC, CD, DA;
AB.firstPoint = cornersArray[0]; AB.secondPoint = cornersArray[1];
BC.firstPoint = cornersArray[1]; BC.secondPoint = cornersArray[2];
CD.firstPoint = cornersArray[2]; CD.secondPoint = cornersArray[3];
DA.firstPoint = cornersArray[3]; DA.secondPoint = cornersArray[0];
Vector3 r1 = new Vector3(-1, -1, -1);
Vector3 r2 = new Vector3(-1, -1, -1);
Vector3 r3 = new Vector3(-1, -1, -1);
Vector3 r4 = new Vector3(-1, -1, -1);
/*
0,0 |----------------| w,0
| |
| |
0,h |________________| w,h
*/
IntersectionPointBetweenLines(AB, line, ref r1);
IntersectionPointBetweenLines(BC, line, ref r2);
IntersectionPointBetweenLines(CD, line, ref r3);
IntersectionPointBetweenLines(DA, line, ref r4);
List<Vector3> points = new List<Vector3>();
points.Add(r1);
points.Add(r2);
points.Add(r3);
points.Add(r4);
points.RemoveAll(
t => ((t.x == -1) && (t.y == -1) && (t.z == -1))
);
if (points.Count == 2)
{
FirstPoint = points[0];
SecondPoint = points[1];
}
}
public Line3DResult IntersectionPointBetweenLines(lineSegment first, Line aSecondLine, ref Vector3 result)
{
Vector3 p1 = first.firstPoint;
Vector3 n1 = first.secondPoint - first.firstPoint;
Vector3 p2 = aSecondLine.point;
Vector3 n2 = aSecondLine.direction;
bool parallel = AreLinesParallel(first, aSecondLine);
if (parallel)
{
return Line3DResult.Line3DResult_Parallel;
}
else
{
float d = 0, dt = 0, dk = 0;
float t = 0, k = 0;
if (Math.Abs(n1.x * n2.y - n2.x * n1.y) > float.Epsilon)
{
d = n1.x * (-n2.y) - (-n2.x) * n1.y;
dt = (p2.x - p1.x) * (-n2.y) - (p2.y - p1.y) * (-n2.x);
dk = n1.x * (p2.x - p1.x) - n1.y * (p2.y - p1.y);
}
else if (Math.Abs(n1.z * n2.y - n2.z * n1.y) > float.Epsilon)
{
d = n1.z * (-n2.y) - (-n2.z) * n1.y;
dt = (p2.z - p1.z) * (-n2.y) - (p2.y - p1.y) * (-n2.z);
dk = n1.z * (p2.z - p1.z) - n1.y * (p2.y - p1.y);
}
else if (Math.Abs(n1.x * n2.z - n2.x * n1.z) > float.Epsilon)
{
d = n1.x * (-n2.z) - (-n2.x) * n1.z;
dt = (p2.x - p1.x) * (-n2.z) - (p2.z - p1.z) * (-n2.x);
dk = n1.x * (p2.x - p1.x) - n1.z * (p2.z - p1.z);
}
t = dt / d;
k = dk / d;
result = n1 * t + p1;
// Check if the point on the segmaent or not
// if (! isPointOnSegment(first, result))
//{
// result = new Vector3(-1,-1,-1);
// }
return Line3DResult.Line3DResult_SkewCross;
}
}
private bool AreLinesParallel(lineSegment first, Line aSecondLine)
{
Vector3 vector = (first.secondPoint - first.firstPoint);
vector.Normalize();
float kl = 0, km = 0, kn = 0;
if (vector.x != aSecondLine.direction.x)
{
if (vector.x != 0 && aSecondLine.direction.x != 0)
{
kl = vector.x / aSecondLine.direction.x;
}
}
if (vector.y != aSecondLine.direction.y)
{
if (vector.y != 0 && aSecondLine.direction.y != 0)
{
km = vector.y / aSecondLine.direction.y;
}
}
if (vector.z != aSecondLine.direction.z)
{
if (vector.z != 0 && aSecondLine.direction.z != 0)
{
kn = vector.z / aSecondLine.direction.z;
}
}
// both if all are null or all are equal, the lines are parallel
return (kl == km && km == kn);
}
private bool isPointOnSegment(lineSegment segment, Vector3 point)
{
//(x - x1) / (x2 - x1) = (y - y1) / (y2 - y1) = (z - z1) / (z2 - z1)
float component1 = (point.x - segment.firstPoint.x) / (segment.secondPoint.x - segment.firstPoint.x);
float component2 = (point.y - segment.firstPoint.y) / (segment.secondPoint.y - segment.firstPoint.y);
float component3 = (point.z - segment.firstPoint.z) / (segment.secondPoint.z - segment.firstPoint.z);
if ((component1 == component2) && (component2 == component3))
{
return true;
}
else
{
return false;
}
}
#endregion
}
}
static void Main(string[] args)
{
//// create the first plane points
Vector3 point11 =new Vector3(-255.5f, -160.0f,-1.5f) ; //0,0
Vector3 point21 = new Vector3(256.5f, -160.0f, -1.5f); //0,w
Vector3 point31 = new Vector3(256.5f, -160.0f, -513.5f); //h,0
Vector3 point41 = new Vector3(-255.5f, -160.0f, -513.5f); //w,h
plane_test plane1 = new plane_test(point11, point21, point41, point31);
//// create the Second plane points
Vector3 point12 = new Vector3(-201.6289f, -349.6289f, -21.5f);
Vector3 point22 =new Vector3(310.3711f,-349.6289f,-21.5f);
Vector3 point32 = new Vector3(310.3711f, 162.3711f, -21.5f);
Vector3 point42 =new Vector3(-201.6289f,162.3711f,-21.5f);
plane_test plane2 = new plane_test(point12, point22, point42, point32);
plane2.Intersection(plane1);
}
and this is test values
Best regards
You need to specify one thing first:
by 3D rectangle, you mean plane rectangle on a 3D plane. (not a
rectangular prism).
Let's say your rectangles are not coplanar nor parallele, and therefore there is one unique line D1 that represents the intersection of the plane described by each rectangle.
Given this assumption their are 4 possible situations for the intersection of 2 rectangles R1 and R2:
(note: sometimes D1 doesn't intersect neither R1 nor R2 and R1 , R2 can be rotated a little bit so D1 doesn't always intersect on parallele sides, but consecutive sides)
When there is an intersection between the 2 rectangles, D1 always intersect R1 and R2 on the same intersection (cf 1st and 2nd picture)
Your model is not good because your line cannot be parallele to 3 segments of the same rectangle...
As you asked in this question : 3D lines intersection algorithm once you have D1 ( Get endpoints of the line segment defined by the intersection of two rectangles ) just determinate the intersection with each segment of the rectangle.(The 4 segments of each rectangles need to be checked)
Then check for common intersection... if you find one then your rectangles intersect.
Sorry it's very hard to directly check the code, but I guess with these peaces of information you should be able to find the error.
Hope it helps.
EDIT:
define a rectangle by a point and 2 vectors :
R2 {A ,u ,v}
R1 {B, u',v'}
define the planes described by R1 and R2 : P1 and P2
One orthogonal vector to P1(resp. P2) is n1 (resp. n2).Let n1 = u ^ v and n2 = u' ^ v' with :
then
P1: n1.(x-xA,y-yA,z-zA)=0
P2: n2.(x-xB,y-yB,z-zB)=0
Then if you're just looking for D1 the equation of D1 is :
D1: P1^2 + P2 ^2 =0 (x,y,z verify P1 =0 an P2 =0 )
D1 : n1.(x-xA,y-yA,z-zA)^2 + n2.(x-xB,y-yB,z-zB)^2 =0
(so just with the expression of your rectangles you can get the equation of D1 with a closed formula.)
Now let's look at the intersections :
the 4 points in R1 are :
{ A , A+u , A+v, A+u+v }
as describe in 3D lines intersection algorithm do :
D1 inter [A,A+u] = I1
D1 inter [A,A+v] = I2
D1 inter [A+u,A+u+v] = I3
D1 inter [A+v,A+u+v] = I4
(I1,I2,I3,I4 can be null)
same for D2 you get I1' I2' I3' I4'
if Ij'=Ik' != null then it's an intersection point
if you did that correctly step by step you should get to the correct solution; unless I didn't fully understand the question...
The program computes the line of intersection of the planes passing through two rectangles. The program then looks for intersections between this line and the edges of one of the rectangles. It returns two points of intersection of such two points are found. I'm not going to debate whether this is a sensible thing to do since I don't know the context of the program.
Let's go through the code and look for things that could be wrong.
The program computes the line passing through the two planes like this:
Vector3 LineDirection = Normal.Cross(SecondOne.Normal);
float d1 = this.GetDistance(LineDirection);
float d2 = SecondOne.GetDistance(LineDirection);
temp = (LineDirection - (this.Normal * d1) - (SecondOne.Normal * d2));
temp.x = Math.Abs((float)Math.Round((decimal)FirstPoint.x, 2));
temp.y = Math.Abs((float)Math.Round((decimal)FirstPoint.y, 2));
Line line;
line.direction = LineDirection;
line.point = temp;
The computation of the line direction is OK, but the computation of point is wrong, as you probably know. But I'll pretend we have a valid point and direction and carry on with the rest of the program.
The program calls AreLinesParallel() to get rid of edges that a parallel to the line through the planes. The code looks like this:
Vector3 vector = (first.secondPoint - first.firstPoint);
vector.Normalize();
float kl = 0, km = 0, kn = 0;
if (vector.x != aSecondLine.direction.x)
{
if (vector.x != 0 && aSecondLine.direction.x != 0)
{
kl = vector.x / aSecondLine.direction.x;
}
}
if (vector.y != aSecondLine.direction.y)
{
if (vector.y != 0 && aSecondLine.direction.y != 0)
{
km = vector.y / aSecondLine.direction.y;
}
}
if (vector.z != aSecondLine.direction.z)
{
if (vector.z != 0 && aSecondLine.direction.z != 0)
{
kn = vector.z / aSecondLine.direction.z;
}
}
// both if all are null or all are equal, the lines are parallel
return ((kl == km && km == kn));
The code more or less checks that the elements of the direction of the edge divided by the elements of the direction of the line are all equal to each other. It's a dangerous procedure to rely on. Because of round-off errors, later procedures may still, say, divide by zero, even if AreLinesParallel() claims that the lines aren't really parallel. It is better not to use the procedure at all.
Now comes the meat of the code, a test for intersection between the edge and the line:
float d = 0, dt = 0, dk = 0;
float t = 0, k = 0;
if (Math.Abs(n1.x * n2.y - n2.x * n1.y) > float.Epsilon)
{
d = n1.x * (-n2.y) - (-n2.x) * n1.y;
dt = (p2.x - p1.x) * (-n2.y) - (p2.y - p1.y) * (-n2.x);
dk = n1.x * (p2.x - p1.x) - n1.y * (p2.y - p1.y);
}
else if (Math.Abs(n1.z * n2.y - n2.z * n1.y) > float.Epsilon)
{
d = n1.z * (-n2.y) - (-n2.z) * n1.y;
dt = (p2.z - p1.z) * (-n2.y) - (p2.y - p1.y) * (-n2.z);
dk = n1.z * (p2.z - p1.z) - n1.y * (p2.y - p1.y);
}
else if (Math.Abs(n1.x * n2.z - n2.x * n1.z) > float.Epsilon)
{
d = n1.x * (-n2.z) - (-n2.x) * n1.z;
dt = (p2.x - p1.x) * (-n2.z) - (p2.z - p1.z) * (-n2.x);
dk = n1.x * (p2.x - p1.x) - n1.z * (p2.z - p1.z);
}
t = dt / d;
k = dk / d;
result = n1 * t + p1;
A mistake of this code is the lack of a comment that explains the origin of the algorithm. If there is no documented algorithm to refer to, the comment can contain the derivation leading to the formulas. The first branch deals with (x, y), the second with (y, z) and the third with (z, x), so I assume that the branches solve for intersection in 2D and lift these findings to 3D. It computes determinants to check for parallel lines for each 2D projection. I shouldn't have to do this kind of reverse engineering.
Anyway, this is the code that produces the NaN values. None of the three branches are triggered, so d = 0 in the end, which gives a division by zero. Instead of relying on AreLinesParallel() to avoid division by zero, it's is better to check the value that actually matters, namely d.
Of course, the code still needs more work, because we don't know yet if the lines are crossing in 3D too. Also the point is on the edge only if 0 <= t && t <= 1. And probably more bugs will show up as the earlier ones are being fixed.
Hello
This is my function that will detect hit.
private int[] GetSelected(int x, int y)
{
const int max = 512;
var Hit_Buffer = new int[max];
var viewport = new int[4];
Gl.glSelectBuffer(max, Hit_Buffer);
Gl.glRenderMode(Gl.GL_SELECT);
Gl.glMatrixMode(Gl.GL_PROJECTION);
Gl.glPushMatrix();
Gl.glLoadIdentity();
Glu.gluLookAt(Distance * Math.Cos(beta) * Math.Cos(alpha)
, Distance * Math.Cos(beta) * Math.Sin(alpha)
, Distance * Math.Sin(beta)
, 0, 0, 0
, -Math.Sin(beta) * Math.Cos(alpha)
, -Math.Sin(beta) * Math.Sin(alpha)
, Math.Cos(beta));
Gl.glGetIntegerv(Gl.GL_VIEWPORT, viewport);
Glu.gluPickMatrix(x, viewport[3] - y, 1, 1, viewport);
Glu.gluPerspective(fovY, ogl1.Width / (double)(ogl1.Height != 0 ? ogl1.Height : 1), 0.1, 100.0);
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glInitNames();
// render scene: a TRIANGLE
Gl.glPushName(1);
Gl.glBegin(Gl.GL_TRIANGLES);
Gl.glVertex3d(0, 0, 0);
Gl.glVertex3d(0, 1, 0);
Gl.glVertex3d(1, 0, 0);
Gl.glEnd();
Gl.glPopName();
//
Gl.glMatrixMode(Gl.GL_PROJECTION);
Gl.glPopMatrix();
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glFlush();
var hits = Gl.glRenderMode(Gl.GL_RENDER);
Array.Resize(ref Hit_Buffer, hits);
return Hit_Buffer;
}
I am drawing a triangle in xy plane.
In gluLookAt, beta is camera angle from xy plane and alpha is camera angle about z.
But it just works if beta be small ( -15< beta <15 degree )!
What is wrong here?
In OpenGL Y is up
For the first 3 parameters, I'd rather say :
Distance * Math.Cos(beta) * Math.Cos(alpha)
, Distance * Math.Sin(beta)
, Distance * Math.Cos(beta) * Math.Sin(alpha)
And for the 3 last ones, try with (0,1,0) first.
Have you called gluLookAt before gluPickMatrix on purpose. Do you want to apply translations to your pick matrix? If not can you move gluPickMatrix immediately after glLoadIdentity and try if your problem gets resolved?
Hello again
I finally find my answer by myself! (by googling)
Here is my corrected code
private int[] GetSelected(int x, int y, bool debug)
{
var hits = 0; // number of hits
// Define select buffer
const int max = 512;
var Hit_buffer = new int[max];
Gl.glSelectBuffer(max, Hit_buffer);
var viewport = new int[4];
Gl.glViewport(0, 0, Width, (Height != 0 ? Height : 1));
Gl.glGetIntegerv(Gl.GL_VIEWPORT, viewport);
if (debug)// show real scene in debug mode
GlDraw();
if(!debug)
Gl.glRenderMode(Gl.GL_SELECT);
int s = debug ? 60 : 3; // test region size
Gl.glLoadIdentity();
Gl.glMatrixMode(Gl.GL_PROJECTION);
Gl.glPushMatrix();
Gl.glLoadIdentity();
Glu.gluPickMatrix(x, viewport[3] - y, s, s, viewport);
Glu.gluPerspective(fovY, Width / (double)(Height != 0 ? Height : 1), 0.1, 1000.0);
Gl.glMatrixMode(Gl.GL_MODELVIEW);
if (debug) // test region will be shown in left-bottom corner
Gl.glViewport(0, 0, s, s);
#region camera
Gl.glTranslated(Dx, Dy, 0);
var Distance = this.Distance;// *ogl1.Height / 60.0;
var CenterView = this.CenterView.Duplicate();
Glu.gluLookAt(Distance * Math.Cos(beta) * Math.Cos(alpha) + CenterView.x
, Distance * Math.Cos(beta) * Math.Sin(alpha) + CenterView.y
, Distance * Math.Sin(beta) + CenterView.z
, CenterView.x
, CenterView.y
, CenterView.z
, -Math.Sin(beta) * Math.Cos(alpha)
, -Math.Sin(beta) * Math.Sin(alpha)
, Math.Cos(beta));
#endregion
if (debug) // draw a bacground in left-bottom corner
{
ChangeColor(Color.Blue);
Glu.gluSphere(Glu.gluNewQuadric(), 50, 50, 50);
Gl.glBegin(Gl.GL_QUADS);
Gl.glVertex3d(-10, -10, -10);
Gl.glVertex3d(-10, 10, -10);
Gl.glVertex3d(10, 10, -10);
Gl.glVertex3d(10, -10, -10);
Gl.glEnd();
}
Gl.glInitNames();
// render scene
foreach (var b in Bodies)
{
Gl.glPushName(b.id);
var bb = b.Duplicate();
bb.color = Color.Red;
bb.Draw();
Gl.glPopName();
}
//
Gl.glMatrixMode(Gl.GL_PROJECTION);
Gl.glPopMatrix();
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glFlush();
if (!debug)
hits = Gl.glRenderMode(Gl.GL_RENDER);
// process hits
int[] Res = { };
int startRecord = 0;
for (int i = 0; i < hits; i++)
{
for (int j = 0; j < Hit_buffer[startRecord]; j++)
{
Array.Resize(ref Res, Res.Length + 1);
Res[Res.Length - 1] = Hit_buffer[startRecord + 3 + j];
}
startRecord += 3 + Hit_buffer[startRecord];
}
return Res;
}
If you set the debug parameter =true, it will draw the select region in left-bottom corner.
The code may be boring, so I am gonna write main parts here
// Define select buffer
const int max = 512;
var Hit_buffer = new int[max];
Gl.glSelectBuffer(max, Hit_buffer);
var viewport = new int[4];
Gl.glGetIntegerv(Gl.GL_VIEWPORT, viewport);
Gl.glRenderMode(Gl.GL_SELECT);
Gl.glLoadIdentity();
Gl.glMatrixMode(Gl.GL_PROJECTION);
Gl.glPushMatrix();
Gl.glLoadIdentity();
Glu.gluPickMatrix(x, viewport[3] - y, 3, 3, viewport);
Glu.gluPerspective(fovY, Width / (double)(Height != 0 ? Height : 1), 0.1, 1000.0);
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glInitNames();
// set camera (gluLookAt, ...) & draw scene
Gl.glMatrixMode(Gl.GL_PROJECTION);
Gl.glPopMatrix();
Gl.glMatrixMode(Gl.GL_MODELVIEW);
Gl.glFlush();
var hits = Gl.glRenderMode(Gl.GL_RENDER);
// process hits
I'm writing my own 3D engine and I have this matrix to make a perspective look. (It's a standard matrix so there is nothing interesting)
public static Matrix3D PrespectiveFromHV(double fieldOfViewY, double aspectRatio, double zNearPlane, double zFarPlane, double mod)
{
double height = 1.0 / Math.Tan(fieldOfViewY / 2.0);
double width = height / aspectRatio;
double d = zNearPlane - zFarPlane;
var rm = Math.Round(mod, 1);
var m = new Matrix3D(
width, 0, 0, 0,
0, height, 0, 0,
0, 0, (zFarPlane / d) * rm, (zNearPlane * zFarPlane / d) * rm,
0, 0, (-1 * rm), (1 - rm)
);
return m;
}
I could make my scene look 2D like just by ignoring that matrix.
But want to do is to make smooth transition from 3D to 2D and back...
Any one have any idea? What do I have to change in this matrix to make smooth transitions possible?
I would use interpolation between m and the indentity matrix I, like so:
Let alpha go from 1 to 0 in alpha*m+(1-alpha)*I
EDIT:
could you please elaborate on
I could make my scene look 2D like
just by ignoring that matrix.
The idea is to intpolate between 2D (by ignoring the matrix) and 3D (using the matrix). If you explain how exactly you ignore the matrix, the interpolation should be straigtforward.
Well.. to ignore "prespective" matrix i do two things... first of all i ignore prespective matrix in matrix callculation
var matrix =
Matrix3DHelper.RotateByDegrees(renderParams.AngleX, renderParams.AngleY, renderParams.AngleZ) *
perspectiveMaterix;
i just don't use perspectiveMatrix...
and second step.. i ignore 'W' parameter when progectin point to the screen
private Point3D GetTransformedPoint(Point3D p, Matrix3D m)
{
double w = (((m.M41 * p.X) + (m.M42 * p.Y)) + (m.M43 * p.Z)) + m.M44;
double x = ((((m.M11 * p.X) + (m.M12 * p.Y)) + (m.M13 * p.Z)) + m.M14) / (w);
double y = ((((m.M21 * p.X) + (m.M22 * p.Y)) + (m.M23 * p.Z)) + m.M24) / (w);
double z = (((m.M31 * p.X) + (m.M32 * p.Y)) + (m.M33 * p.Z)) + m.M34;
return new Point3D(x, y, z, w);
}