I have an origin point, and normal vector of a 3D plane. I want to calculate 4 vertices to use as the corners of a 2 triangle quad to render the plane.
-
1\$\begingroup\$ Looks like you're kind of missing a vector here. Without it, there is an infinite possibility of placement (as if you would "rotate" your quad around that normal vector). \$\endgroup\$– Vaillancourt ♦Mar 6, 2020 at 3:20
-
\$\begingroup\$ could I get a second vector parallel to the plane by getting a perpendicular vector to the normal? using dot or cross ? \$\endgroup\$– kevzettlerMar 6, 2020 at 20:16
-
1\$\begingroup\$ Jay gives you a solution for what you propose; this will give you a quad. If that's all you need, thats great! It may not be oriented the way you want. I suggest you try it and either comment on the answer, edit your question or post a follow up question if you need something more precise. \$\endgroup\$– Vaillancourt ♦Mar 6, 2020 at 20:52
-
1\$\begingroup\$ Planes by definition are infinite and don't have corners, you also need to provide some sort of value to determine the side length(s) of the quad you want to take from the plane. (Also is the quad supposed to be square? Right angles? Parallelogram?) \$\endgroup\$– RomenOct 14, 2020 at 20:03
2 Answers
Here's one way that I can think of to do it.
Find a point on the plane a
other than the origin, o
. The vector a - o
lies on the plane, so is guaranteed perpendicular to the plane normal. Normalise the vector a - o
and call it x
.
Take the cross product of x
and the plane normal and call it y
. y
is guaranteed to be perpendicular to both x
and the plane normal.
The four points of a square are now o + x
, o + y
, o - x
, o - y
.
I had to go through a similar situation lately, and here is something that might be more complete than the provided answer.
You provide a normal and the origin. Using the origin is a very specific case, so I'll use "point on the plane", or simply point, which is a bit more generic.
Let's start by going into standards and finding the plane equation parameters. According to the Internet and a few books out there, we have:
ax + by + cz + d = 0
where a
, b
and c
represent the normal of the plane, x
, y
and z
represent a point on the plane, and d
is the shortest distance from the origin to the plane, or the distance you'd have to move the plane toward the normal so that plane passes by the origin.
We can easily find d
with this:
d = -(normal.x * point.x + normal.y * point.y + normal.z * point.z)
Now the tricky part is to "place a quad on the plane". Keep in mind that there is an infinite possibility of having that, as you don't specify an "axis around which you'll place it". We'll start by placing our quad on the XY plane, and rotate and move it according to the parameters we have above.
The following function is based on ODE's function dxPlaneSpace
. There was comments lacking so if any of those can help anyone, it's all good.
void getPlaneSpace( const Vec3& aNormal, Vec3& aP, Vec3& aQ )
{
// This is an arbitrary way to avoid a division by zero (this guarantees that z > 0,
// used when computing a).
if ( std::abs( aNormal.z() ) > ( 1.0 / std::sqrt( 2.0 ) ) ) // 0.707106781186547524...
{ // Note: the "else" part is a bit easier to follow as it projects on the x-y plane.
// choose p in y-z plane, where the magnitude of x is zero
// a is the length^2 of the normal projected onto the plane
float a = aNormal.y() * aNormal.y() + aNormal.z() * aNormal.z();
// k is 1 / length of the normal vector projected onto the y-z plane.
float k = 1 / std::sqrt( a );
aP.x() = 0;
aP.y() = -aNormal.z() * k;
aP.z() = aNormal.y() *k;
// set q = n x p
aQ.x() = a * k;
aQ.y() = -aNormal.x() * aP.z();
aQ.z() = aNormal.x() * aP.y();
}
else
{
// choose p in x-y plane, where the z = 0
// get the length^2 of the normal projected onto the plane.
float a = aNormal.x() * aNormal.x() + aNormal.y() + aNormal.y();
// k is 1 / length; used to adjust the size of the new vectors so that they're "unit"
// vector.
float k = 1 / std::sqrt( a );
aP.x() = -aNormal.y() * k;
aP.y() = aNormal.x() * k;
aP.z() = 0;
// set q = n x p
aQ.x() = -aNormal.z() * aP.y();
aQ.y() = aNormal.z() * aP.x();
aQ.z() = a * k;
}
}
void getRotationMatrixFromZAxis( Matrix3& aRot, float aX, float aY, float aZ )
{
Vec3f normal { aX, aY, aZ };
Vec3f p;
Vec3f q;
normal.normalize();
// This will create a reference frame for the supplied normal:
getPlaneSpace( normal, p, q );
aRot.set(
p.x(), q.x(), normal.x(),
p.y(), q.y(), normal.y(),
p.z(), q.z(), normal.z()
);
}
This will generate a vertex array that can be used to produce two triangles of the quad, and the normals for that quad.
std::pair<Vec3Array, Vec3Array> GetQuadCoordinatesOnPlane(
Vec4d aPlane
, float aQuadHalfSize )
{
// The normals is easy:
Vec3Array vec3ArrayNormals = new Vec3Array( 6 );
Vec3 normal {
static_cast<float>( aPlane.x() )
, static_cast<float>( aPlane.y() )
, static_cast<float>( aPlane.z() ) };
float d = static_cast<float>( aPlane.w() );
( *vec3ArrayNormals )[0] = normal;
( *vec3ArrayNormals )[1] = normal;
( *vec3ArrayNormals )[2] = normal;
( *vec3ArrayNormals )[3] = normal;
( *vec3ArrayNormals )[4] = normal;
( *vec3ArrayNormals )[5] = normal;
// We need 4 vertices, we'll use those to form 2 triangles; we start off assuming
// they're in the XY plane
Vec3Array vec3ArrayVertices = new Vec3Array( 4 );
( *vec3ArrayVertices )[0] = Vec3f( aQuadHalfSize, aQuadHalfSize, 0.0f );
( *vec3ArrayVertices )[1] = Vec3f( -aQuadHalfSize, aQuadHalfSize, 0.0f );
( *vec3ArrayVertices )[2] = Vec3f( -aQuadHalfSize, -aQuadHalfSize, 0.0f );
( *vec3ArrayVertices )[3] = Vec3f( aQuadHalfSize, -aQuadHalfSize, 0.0f );
Matrix3 rotationMatrix;
getRotationMatrixFromZAxis(
rotationMatrix
, static_cast<float>( aPlane.x() )
, static_cast<float>( aPlane.y() )
, static_cast<float>( aPlane.z() ) );
// We need a matrix4 for the vector transformation.
Matrix4 rotat;
for ( int i = 0; i < 3; ++i )
{
for ( int j = 0; j < 3; j++ )
rotat( i, j ) = rotationMatrix( i, j );
}
// Transform the vertices.
Vec3f planeOrigin = normal * -d;
for ( int i = 0; i < 4; ++i )
{
Vec4f vec { ( *vec3ArrayVertices )[i], 1 };
vec = rotat * vec;
( *vec3ArrayVertices )[i].x() = vec.x() + planeOrigin.x();
( *vec3ArrayVertices )[i].y() = vec.y() + planeOrigin.y();
( *vec3ArrayVertices )[i].z() = vec.z() + planeOrigin.z();
}
// Build the triangles.
Vec3Array trianglesArrayVertices = new Vec3Array( 6 );
( *trianglesArrayVertices )[0] = ( *vec3ArrayVertices )[0];
( *trianglesArrayVertices )[1] = ( *vec3ArrayVertices )[1];
( *trianglesArrayVertices )[2] = ( *vec3ArrayVertices )[2];
( *trianglesArrayVertices )[3] = ( *vec3ArrayVertices )[2];
( *trianglesArrayVertices )[4] = ( *vec3ArrayVertices )[3];
( *trianglesArrayVertices )[5] = ( *vec3ArrayVertices )[0];
return std::pair( trianglesArrayVertices, vec3ArrayNormals );
}