1
\$\begingroup\$

I should clarify that I understand there is an infinite amount of 'perpendicular' vectors in 3D space. I am creating an electricity effect, and to do this the ability to have any number of perpendicular vectors is a good thing. The hard part is how to select any random one of these vectors - how would I do that, given a base vector "direction" which has been normalised?

Keep in mind that I want the resultant vector to be random, not just always up and down on the y-axis or anything.

\$\endgroup\$
3
\$\begingroup\$

Here's a Unity-specific option in the same vein as sam hocevar's answer:

Vector3 GetRandomUnitPerpendicular(Vector3 v)
{
    float angle = Random.Range(0, Mathf.PI * 2f);

    // Generate a uniformly-distributed unit vector in the XY plane.
    Vector3 inPlane = new Vector3(Mathf.Cos(angle), Mathf.Sin(angle), 0f);

    // Rotate the vector into the plane perpendicular to v and return it.
    return Quaternion.LookRotation(v) * inPlane;
}

By construction, this returns a unit vector. Rotating by a Quaternion.LookRotation is a bit overkill here, but it lets us pass off the problem of picking a basis to existing code, rather than duplicating it, keeping the code concise.

Note that these methods based on a random angle will have better uniformity than those based on generating a random vector in a square or cube (which have a higher probability of giving vectors pointing toward the corners than toward the sides), and don't need special handling for cases when the pseudorandom input is all zeroes.

\$\endgroup\$
  • \$\begingroup\$ This is arguably better than my answer. I think you could push the idea even further with something like: return Quaternion.LookRotation(v) * Quaternion.AngleAxis(Random.Range(0, 360f), Vector3.forward) * Vector3.right; \$\endgroup\$ – sam hocevar May 6 '16 at 6:32
1
\$\begingroup\$

I would do the following:

  • have your vector u (ensure it’s normalised)
  • pick an orthogonal vector v using any existing method (ensure it’s normalised)
  • pick a random angle α
  • a good random unit vector is therefore: v·cos(α) + (u × v)·sin(α)

If you wish your vector to also have random length, you can then multiply it by a random number, or possibly by the square root of a random number depending on the kind of distribution you’re looking for.

\$\endgroup\$
0
\$\begingroup\$

We know that the cross product of two vectors in 3-dimensional space is another vector which is orthogonal to both original vectors.

Given a vector which starts at the origin, we can just pick any random vector which starts at the origin to do the cross-product against.

Since the cross-product produces a vector which is orthogonal to both, and we picked a random vector as the second input, we have a vector which is both in a random direction and orthogonal to the original input vector.

NOTE: As DMGregory points out in the comments, you can sometimes select a random vector that is parallel to the original one. For this case, check if the result of the cross-product is a zero vector. If it is, then picking a random offset from the input vector should suffice.

Example (JavaScript and THREE.js). The red arrow is the original direction vector, the green arrow the random orthogonal one. The blue circle is the plane orthogonal to the red arrow:

var con = document.getElementById('container');

var renderer, scene, camera;

renderer = new THREE.WebGLRenderer();
renderer.setSize(320, 240);
con.appendChild(renderer.domElement);

scene = new THREE.Scene();

camera = new THREE.PerspectiveCamera(45, 4 / 3, 0.1, 1000);
camera.position.set(2, 1, 10);
camera.lookAt(0, 0, 0);

function randPoint(){
  return Math.random() * 2 - 1;
}

// == Example ==
var vec1, vec2, vec3;

// Direction Vector
vec1 = new THREE.Vector3(2, 2, -2);
vec1.normalize();

// Random Vector
vec2 = new THREE.Vector3(randPoint(), randPoint(), randPoint());
vec2.normalize();

// Resultant Random Orthogonal Vector
// ===================================
vec3 = new THREE.Vector3();
vec3.crossVectors(vec1, vec2);

// Modify if needed. Offset vec1
// to ensure a vector which is not parallel.
if (vec3.x == 0 && vec3.y == 0 && vec3.z == 0){
  vec2 = new THREE.Vector3(
    vec1.x + Math.random() + 0.001,
    vec1.y + Math.random() + 1.001,
    vec1.z + Math.random() + 2.001
  );
  vec2.normalize();
  vec3.crossVectors(vec1, vec2);
}

vec3.normalize();
// ===================================

var origin = new THREE.Vector3(0, 0, 0);

var cGeo = new THREE.CircleGeometry(5, 32);
var cMesh = new THREE.Mesh(cGeo, new THREE.MeshBasicMaterial({color: 0x0000ff, wireframe: true}));
cMesh.position.set(0, 0, 0);
cMesh.lookAt(vec1);
scene.add(cMesh);

scene.add(new THREE.ArrowHelper(vec1, origin, 5, 0xff0000));
scene.add(new THREE.ArrowHelper(vec3, origin, 5, 0x00ff00));


renderer.render(scene, camera);
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r73/three.min.js"></script>
<div id='container'></div>

\$\endgroup\$
  • \$\begingroup\$ This has a rare glitch if the generated vec2 is parallel to vec1 - then you'll get a zero vector for vec3 which can't be meaningfully normalized. \$\endgroup\$ – DMGregory May 4 '16 at 15:38
  • \$\begingroup\$ @DMGregory true. In that case, one could check the cross product and modify the random vector if needed. \$\endgroup\$ – XNargaHuntress May 4 '16 at 16:06
0
\$\begingroup\$

You are looking for any vector that is completely in the plane described by a normal vector. Let's call your normal vector n, and the result vector (what you want to calculate) r. You know something about the relationship between the two vectors, namely that their dot product must be 0. That's one equation, but you have 3 unknowns here (the x, y, and z components of r). It turns out that's as much as you can constrain this problem. You will need to randomly select two coordinates in r and then project the last coordinate into the plane by using the dot product. Here's a simple solution (in pseudo-ish code since I'm not overly familiar with Unity):

Vector3 GetOrthogonalVector(Vector3 n) {
    Vector3 r(Random.value, Random.value);
    r.z = -(r.x*n.x + r.y*n.y)/n.z;
    return r.normalized;
}

Of course, you need to be safe and make sure that the z component of n (n.z) is not zero and do something sensible there. Also, as pointed out in the comments, if r.x = r.y = 0, then you will get zero for r.z, so that should be handled as well.

\$\endgroup\$
  • \$\begingroup\$ You'll also need to handle the rare case where r.x = r.y = 0, in which this method will generate a zero vector regardless of n. \$\endgroup\$ – DMGregory May 4 '16 at 16:42
  • \$\begingroup\$ Good point. Added a note. \$\endgroup\$ – dannuic May 4 '16 at 16:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.