Given three 3D points (A,B, & C) how do I calculate the normal vector? The three points define a plane and I want the vector perpendicular to this plane.
Can I get sample C# code that demonstrates this?
It depends on the order of the points. If the points are specified in a counter-clockwise order as seen from a direction opposing the normal, then it's simple to calculate:
Dir = (B - A) x (C - A)
Norm = Dir / len(Dir)
where x is the cross product.
If you're using OpenTK or XNA (have access to the Vector3 class), then it's simply a matter of:
class Triangle {
Vector3 a, b, c;
public Vector3 Normal {
get {
var dir = Vector3.Cross(b - a, c - a);
var norm = Vector3.Normalize(dir);
return norm;
}
}
}
Form the cross-product of vectors BA and BC. See http://mathworld.wolfram.com/CrossProduct.html.
You need to calculate the cross product of any two non-parallel vectors on the surface. Since you have three points, you can figure this out by taking the cross product of, say, vectors AB and AC.
When you do this, you're calculating a surface normal, of which Wikipedia has a pretty extensive explanation.
Related
Given the positions of the vertices, and the surface normals. How can I calculate the area(which may be 0) that the 2 triangles are in contact? These triangles are also in 3D space so if they aren't lined up properly, just jammed into each other, the contact area should be 0.
(In C#)
This is not a trivial problem, so let's break it down into steps.
Check if the two triangles are coplanar, otherwise the area is 0.
Project the triangles onto a 2D surface
Calculate the intersection polygon
Calculate the area
1. Checking for coplanarity
For the two triangles to be coplanar, all vertices of one triangle must lie in the plane determined by the other one.
Using the algorithm described here we can check for every vertex whether that is the case, but due to the fact that floating point numbers are not perfectly precise, you will need to define some error theshold to determine what still counts as coplanar.
Assuming va and vb are the vectors of the triangles A and B respectively, the code could look something like this.
(Note: I have never worked with Unity and am writing all of the code from memory, so please excuse if it isn't 100% correct).
public static bool AreTrianglesCoplanar(Vector3[] va, Vector3[] vb) {
// make sure these are actually triangles
Debug.Assert(va.Length == 3);
Debug.Assert(vb.Length == 3);
// calculate the (scaled) normal of triangle A
var normal = Vector3.Cross(va[1] - va[0], va[2] - va[0]);
// iterate all vertices of triangle B
for(var vertex in vb) {
// calculate the dot product between the normal and the vector va[0] --> vertex
// the dot product will be 0 (or very small) if the angle between these vectors
// is a right angle (90°)
float dot = Vector3.Dot(normal, vertex - va[0]).
// the error threshold
const float epsilon = 0.001f;
// if the dot product is above the threshold, the vertex lies outside the plane
// in that case the two triangles are not coplanar
if(Math.Abs(dot) > epsilon)
return false;
}
return true;
}
2. Projecting the triangles into 2D space
We now know that all six vertices are in the same 2D plane embedded into 3D space, but all of our vertex coordinates are still three-dimensional. So the next step would be to project our points into a 2D coordinate system, such that their relative position is preserved.
This answer explains the math pretty well.
First, we need to find a set of three vectors forming an orthonormal basis (they must be orthoginal to each other and of length 1).
One of them is just the plane's normal vector, so we need two more vectors that are orthogonal to the first, and also orthogonal to each other.
By definition, all vectors in the plane defined by our triangles are orthogonal to the normal vector, so we can just pick one (for example the vector from va[0] to va[1]) and normalize it.
The third vector has to be orthogonal to both of the others, we can find such a vector by taking the cross product of the previous two.
We also need to choose a point in the plane as our origin point, for example va[0].
With all of these parameters, and using the formula from the linked amswer, we can determine our new projected (x, y) coordinates (t_1 and t_2 from the other answer). Note that -- because all of our points lie in the plane defining that normal vector -- the third coordinate (called s in the other answer) will always be (close to) zero.
public static void ProjectTo2DPlane(
Vector3[] va, Vector3[] vb
out Vector2[] vaProjected, out Vector2[] vbProjecte
) {
// calculate the three coordinate system axes
var normal = Vector3.Cross(va[1] - va[0], va[2] - va[0]).normalized;
var e1 = Vector3.Normalize(va[1] - va[0]);
var e2 = Vector3.Cross(normal, e1);
// select an origin point
var origin = va[0];
// projection function we will apply to every vertex
Vector2 ProjectVertex(Vector3 vertex) {
float s = Dot(normal, vertex - origin);
float t1 = Dot(e1, vertex - origin);
float t2 = Dot(e2, vertex - origin);
// sanity check: this should be close to zero
// (otherwise the point is outside the plane)
Debug.Assert(Math.Abs(s) < 0.001);
return new Vector2(t1, t2);
}
// project the vertices using Linq
vaProjected = va.Select(ProjectVertex).ToArray();
vbProjected = vb.Select(ProjectVertex).ToArray();
}
Sanity check:
The vertex va[0] should be projected to (0, 0).
The vertex va[1] should be projected to (*, 0), so somewhere on the new x axis.
3. / 4. Calculating the intersection area in 2D
This answer this answer mentions the algorithms necessary for the last step.
The Sutherland-Hodgman algorithm successively clips one triangles with each side of the other. The result of this will be either a triangle, a quadrilateral or an empty polygon.
Finally, the shoelace formula can be used to calculate the area of the resulting clipping polygon.
Bringing it all together
Assuming you implemented the two functions CalculateIntersecionPolygon and CalculatePolygonArea, the final intersection area could be calculated like this:
public static float CalculateIntersectionArea(Mesh triangleA, Mesh triangleB) {
var verticesA = triangleA.GetVertices();
var verticesB = triangleB.GetVertices();
if(!AreTrianglesCoplanar(verticesA, verticesB))
return 0f; // the triangles are not coplanar
ProjectTo2DPlane(verticesA, verticesB, out Vector2[] projectedA, out Vector2[] projectedB);
CalculateIntersecionPolygon(projectedA, projectedB, out List<Vector2> intersection);
if(intersection.Count == 0)
return 0f; // the triangles didn't overlap
return CalculatePolygonArea(intersection);
}
First off apologies for the crude drawing, I am by no means well versed in 3D manipulation and have only a very basic understanding of matrices, so please explain everything as clearly as you can and make no assumptions of my level of knowledge.
I am currently gathering longitude and latitude data and converting it into Cartesian X,Y and Z with the following function:
public CartesianXYZ LonLat2Cartesian(double lon, double lat)
{
CartesianXYZ XYZ = new CartesianXYZ();
var R = 6371000;
XYZ.X = Convert.ToInt32(R * Math.Cos(lat) * Math.Cos(lon));
XYZ.Y = Convert.ToInt32(R * Math.Sin(lat) * Math.Cos(lon));
XYZ.Z = Convert.ToInt32(R * Math.Sin(lat));
return XYZ;
}
When plotting these on a graph, I have found that the Y axis varies quite significantly, even though these points are very close to each other, I assume this is due to the fact we are sat on a sphere and unless I was at the north pole, it would be considered slanted, more so the closer to the equator I was. (If this is not the case please do let me know)
Please see the below three points plotted in a 3D scatter graph
Point A would be considered the "central" point where the user is
Point B is a point directly to the south of the user
Point C is a point directly to the north of the user
What I hope to achieve is to rotate points B and C around point A, so that their Y values are the same and I can consider the graph a 2D representation of the points, I found by just changing the Y to the same I ended up with a fairly skewed version of the points.
The end goal is to use the heading of the user and find out if any of the points are within their field of view (for augmented reality)
Any assistance, helpful critique or links to useful articles would be greatly appreciated.
You got wrong equation (did not spot it before :) either) it should be:
public CartesianXYZ LonLat2Cartesian(double lon, double lat)
{
CartesianXYZ XYZ = new CartesianXYZ();
var R = 6371000;
XYZ.X = Convert.ToInt32(R * Math.Cos(lat) * Math.Cos(lon));
XYZ.Y = Convert.ToInt32(R * Math.Cos(lat) * Math.Sin(lon));
XYZ.Z = Convert.ToInt32(R * Math.Sin(lat));
return XYZ;
}
btw this is just sphere which our planet is not so if you need more precise result use WGS84:
How to convert a spherical velocity coordinates into cartesian
I do not know how to approach this problem as I am using API that has Plane data structure that is basically origin point, x y and z vectors that define a plane.
If I have two planes, how can I find bisector plane?
Is there a mathematical description for such plane.
Geometrically I would approach this problem by calculating intersection line between planes and then no idea how to define a point for direction that plane.
Any help would be much appreciated.
Before I tried something like this, and this get what I want, but I am wondering if there is a solution without doing intersections:
public static Plane BisectorPlane(Plane a, Plane b)
{
Rhino.Geometry.Intersect.Intersection.PlanePlane(a, b, out Line lnA);
a.Translate(a.ZAxis);
b.Translate(b.ZAxis);
Rhino.Geometry.Intersect.Intersection.PlanePlane(a, b, out Line lnB);
return new Plane( lnA.From,lnA.To,lnB.PointAt(0.5));
}
I am wondering if it is possible to solve this is not geometrically (calculating intersections) but mathematically.
You already have got line of intersection. Now choose any point P at this line to make base point.
Get direction vector for this line (dL)
Get sum of unit normals for given planes S. This is vector lying in bisector plane and perpendicular to intersection line
S = (a.normal + b.normal)
Now calculate vector product of dL and S to get normal
BisectorN = dL x S
And normalize it (make unit length dividing by its length) if needed
bN = BisectorN.Normalize
Base point P and normal bN define bisector plane.
I tried your approach and it gives bisector plane. But the problem is that planes shifts as it is constructed from origin and normal, not from origin and two x and y axis:
Point3d origin = lnA.PointAt(0.5);
Vector3d S = a.Normal + b.Normal;
Vector3d dL = Vector3d.Subtract((Vector3d)lnA.From, (Vector3d)lnA.To);
Vector3d BisectorN = Vector3d.CrossProduct(dL,S);
BisectorN.Unitize();
return new Plane(origin, BisectorN);
I am using a C# port of libnoise with XNA (I know it's dead) to generate planets.
There is a function in libnoise that receives the coordinates of a vertex in a sphere surface (latitude and longitude) and returns a random value (from -1 to 1).
So with that value, I can change the height of each vertex on the surface of the sphere (the altitude), creating some elevation, simulating the surface of a planet (I'm not simply wrapping a texture around the sphere, I'm actually creating each vertex from scratch).
An example of what I have:
Now I want to animate the sphere, like this
But the thing is, libnoise only works with 3D noise.
The "planet" function maps the latitude and longitude to XYZ coordinates of a cube.
And I believe that, to animate a sphere like I want to, I need an extra coordinate there, to be the "time" dimension. Am I right? Or is it possible to do this with what libnoise offers?
OBS: As I mentioned, I'm using an UV sphere, not an icosphere or a spherical cube.
EDIT: Here is the algorithm used by libnoise to map lat/long to XYZ:
public double GetValue(double latitude, double longitude) {
double x=0, y=0, z=0;
double PI = 3.1415926535897932385;
double DEG_TO_RAD = PI / 180.0;
double r = System.Math.Cos(DEG_TO_RAD * lat);
x = r * System.Math.Cos(DEG_TO_RAD * lon);
y = System.Math.Sin(DEG_TO_RAD * lat);
z = r * System.Math.Sin(DEG_TO_RAD * lon);
return GetNoiseValueAt(x, y, z);
}
An n dimensional noise function takes n independent inputs (i0, i1, ..., in-1, in) & returns a value v, thus 3D noise is sufficient to generate a height map that varies over time. In your case the inputs would be longitude, latitude & time and the output would be the height offset.
The simple general algorithm would be:
at each time step (t){
for each vertex (v) on a sphere centered on some point (c){
calculate the longitude & latitude
get the scalar noise value (n) for the longitude, latitude & time
calculate the new vertex position (p) as follows p = ((v-c)n)+c
}
}
Note: this assumes you are not replacing/modifiying the original vertex values. You could either save a copy of them (uses less computation, but more memory) or recalculate them them based on a distance from c (uses less memory, but more computation). Also, you might get a smoother animation by calculating 2 (or more) larger time steps & interpolating to get the intermediate frames.
To the best of my knowledge, this solution should work for a UV sphere, an icosphere or a spherical cube.
Ok I think I made it.
I just added the time parameter to the mapped XYZ coordinates.
Using the same latitude and longitude but incrementing time by 0.01d gave me a nice result.
Here is my code:
public double GetValue(double latitude, double longitude, double time) {
double x=0, y=0, z=0;
double PI = 3.1415926535897932385;
double DEG_TO_RAD = PI / 180.0;
double r = System.Math.Cos(DEG_TO_RAD * lat);
x = r * System.Math.Cos(DEG_TO_RAD * lon);
y = System.Math.Sin(DEG_TO_RAD * lat);
z = r * System.Math.Sin(DEG_TO_RAD * lon);
return GetNoiseValueAt(x + time, y + time, z + time);
}
If someone has a better solution please share it!
Sorry for the late answer, but I couldn't find a satisfactory answer elsewhere online, so I'm writing this up for anyone who has this problem in the future.
What worked for me was using multiple 3d perlin noise sources, and combining them into 1 single noise source. Adding time to the xyz coordinates just creates a very noticeable effect of terrain moving in the (-1,-1,-1) direction.
Averaging over 4 uncorrelated noise sources does change the noise characteristics a bit, so you might have to adapt some factors to your use case.
This solution still isn't perfect, but I haven't seen any visual artifacts.
Code is C++ libnoise, but it should translate equally well to other languages.
noise::module::Perlin perlin_noise[4];
float get_height(ofVec3f p, float time) {
p*=2;
time /= 10 ;
return (perlin_noise[0].GetValue(p.x, p.y, p.z) +
perlin_noise[1].GetValue(p.x, p.y, time) +
perlin_noise[2].GetValue(p.x, time, p.z) +
perlin_noise[3].GetValue(time, p.y, p.z))/2;
}
Ideally, for a single 3d noise source, you want to multiply you x,y,z coords with a monotonic function of t, such that it explores a constantly expanding sphere surface of the noise source, but I haven't figured out the math yet..
Edit: the framework I use (openframeworks) has a 4d perlin noise function built in ofSignedNoise(glm::vec4)
I need a C# code snippet calculating the surface and vertex normals. Kind of surface is triangulated 3D closed mesh. The required code snippet must be able to use a vertex set and triangleindices. These are ready to use at the moment. The surface of 3D mesh object is not smooth, so it needs to be smoothed.
Could you help me.
It sounds like you're trying to display your 3D mesh and apply a smooth shading appearance by interpolating surface normals, such as in Phong shading, and you need to calculate the normals first. This is different from smoothing the surface of the mesh itself, since that implies altering the positions of its vertices.
Surface normals can be calculated by getting the vector cross product of two edges of a triangle.
As far as code, I'm unaware of any C# examples, but here is one in C++ that should be easy to port. It is taken from the popular NeHe tutorials for OpenGL:
void calcNormal(float v[3][3], float out[3]) // Calculates Normal For A Quad Using 3 Points
{
float v1[3],v2[3]; // Vector 1 (x,y,z) & Vector 2 (x,y,z)
static const int x = 0; // Define X Coord
static const int y = 1; // Define Y Coord
static const int z = 2; // Define Z Coord
// Finds The Vector Between 2 Points By Subtracting
// The x,y,z Coordinates From One Point To Another.
// Calculate The Vector From Point 1 To Point 0
v1[x] = v[0][x] - v[1][x]; // Vector 1.x=Vertex[0].x-Vertex[1].x
v1[y] = v[0][y] - v[1][y]; // Vector 1.y=Vertex[0].y-Vertex[1].y
v1[z] = v[0][z] - v[1][z]; // Vector 1.z=Vertex[0].y-Vertex[1].z
// Calculate The Vector From Point 2 To Point 1
v2[x] = v[1][x] - v[2][x]; // Vector 2.x=Vertex[0].x-Vertex[1].x
v2[y] = v[1][y] - v[2][y]; // Vector 2.y=Vertex[0].y-Vertex[1].y
v2[z] = v[1][z] - v[2][z]; // Vector 2.z=Vertex[0].z-Vertex[1].z
// Compute The Cross Product To Give Us A Surface Normal
out[x] = v1[y]*v2[z] - v1[z]*v2[y]; // Cross Product For Y - Z
out[y] = v1[z]*v2[x] - v1[x]*v2[z]; // Cross Product For X - Z
out[z] = v1[x]*v2[y] - v1[y]*v2[x]; // Cross Product For X - Y
ReduceToUnit(out); // Normalize The Vectors
}
The normalization function ReduceToUnit() can be found there as well.
Note that this calculates a surface normal for a single triangle. Since you give no information about how your vertices and indices are stored, I will leave it up to you to derive the set of triangles you need to pass to this function.
EDIT: As an additional note, I think the "winding direction" of your triangles is significant. Winding in the wrong direction will cause the normal to point in the opposite direction as well.