'How can I rotate a vector 90 degrees into a perpendicular plane, and then 15 degrees free from that plane?

What I ultimately want is a vector, giving the direction of the green line in the image below, knowing only the position of the yellow and green dots.
To be more specific, it's angle can be random as long as it's endpoint ends up somewhere on the green-blue surface of the cylinder. So, 360° free around cylinder, and about 15° limited to the edges of the cylinder.

The cylinder is perpendicular to the line from the yellow and green dot.

Length is not important, only direction.

My main problem is I don't know how to go from vector Yellow to green dot, to any vector perpendicular to it.

PS None of these things are aligned on a x y z axis. That grid is not xyz, just to help visualize. enter image description here



Solution 1:[1]

here is the code: given an angle theta and two points it will give you a vector starting from pointStart perpendicular to the vector from pointStart to pointEnd:

function perpendicularVector(pointStart,pointEnd,theta){
    let vDiff = new THREE.Vector3(0, 0, 0)
        .subVectors(pointEnd, pointStart) 
        .normalize()
  
    let V = new THREE.Vector3(
        vDiff.y + vDiff.x * vDiff.z,
      vDiff.y * vDiff.z -vDiff.x,
      -(vDiff.x * vDiff.x) - vDiff.y * vDiff.y
    )

    return
        V   .applyAxisAngle(vDiff, theta)
            .applyAxisAngle( new THREE.Vector3().multiplyVectors(V, vDiff).normalize(), 15*Math.PI/180 )
}

here is a small showoff of what the above code do: (the snippet is intentionally bad because its there just to show the functionality of the above code)

(you can zoom rotate and pan using the mouse on the render that appears after you click run snippet)

body {
  font-family: sans-serif;
  margin: 0;
  background-color: #e2cba9;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

canvas {
  width: 100%;
  height: 100%;
}
<div id="app"></div>

<script type="module">
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js";

import * as THREE from "https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js";

var scene = new THREE.Scene, theta = 0;
let point1 = new THREE.Vector3(4, 2, 1),
  point2 = new THREE.Vector3(0, 3, 3);

function perpendicularVector(e, n, t) {
  let r = new THREE.Vector3(0, 0, 0).subVectors(n, e).normalize(),
    o = new THREE.Vector3(r.y, -r.x, 0),
    i = new THREE.Vector3(r.x * r.z, r.y * r.z, -r.x * r.x - r.y * r.y);
  var a = o.multiplyScalar(Math.cos(t)).add(i.multiplyScalar(Math.sin(t)));
  return a.add(e), a
}

function pointAtCoords(e, n) {
  let t = new THREE.MeshBasicMaterial({ color: n }),
    r = new THREE.SphereGeometry(.1, 8, 8),
    o = new
  THREE.Mesh(r, t);
  return o.position.add(e), o
}

function lineFromAtoB(e, n, t) {
  let r = new THREE.LineBasicMaterial({ color: t }),
    o = [];
  o.push(e), o.push(n);
  let i = (new THREE.BufferGeometry).setFromPoints(o);
  return new THREE.Line(i, r)
}

var renderer = new THREE.WebGLRenderer({ antialias: !0 });
renderer.setSize(window.innerWidth, window.innerHeight), document.getElementById("app").appendChild(renderer.domElement);
var camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight,
  .1, 1e3);
camera.position.set(7, 7, 8), camera.lookAt(new THREE.Vector3), camera.position.add(new THREE.Vector3(3, 0, 3));
var controls = new OrbitControls(camera, renderer.domElement);

function drawEverything(e) {
  const n = new THREE.AxesHelper(30);
  scene.add(n);
  const t = new THREE.GridHelper(30, 30);
  t.position.add(new THREE.Vector3(15, 0, 15)), scene.add(t);
  const r = new THREE.GridHelper(30, 30);
  r.rotateX(Math.PI / 2), r.position.add(new THREE.Vector3(15, 15, 0)), scene.add(r);
  const o = new THREE.GridHelper(30, 30);
  o.rotateZ(Math.PI / 2), o.position.add(new THREE.Vector3(0, 15, 15)), scene.add(o);
  let i = new THREE.Vector3(0, 0, 0),
    a = perpendicularVector(point1, point2, e);
  scene.add(pointAtCoords(point1, 16776960)), scene.add(pointAtCoords(point2, 65280));
  var d = pointAtCoords(a, 255);
  scene.add(d), scene.add(lineFromAtoB(point1, point2, 16711935)), scene.add(lineFromAtoB(i, point1, 16711680)), scene.add(lineFromAtoB(i, point2, 16711680)), scene.add(lineFromAtoB(point1, a, 65280))
}

function animate() {
   scene = new THREE.Scene, drawEverything(theta += .1), 
   setTimeout((() => {
    requestAnimationFrame(animate)
  }), 1e3 / 30), renderer.render(scene, camera)
}

animate();
</script>

Solution 2:[2]

This is totally achievable with some math calculations. The term you're looking for is "Orthogonal vectors", which means vectors that are perpendicular to each other. The cylinder radius is orthogonal to the line between blue to yellow points.

However, since you're already using Three.js, you can just let it do all the hard work for you with the help of an Object3D.

// Declare vectorA (center, green)
const vecA = new THREE.Vector3(xA, yA, zA);

// Declare vectorB (destination, yellow)
const vecB = new THREE.Vector3(xB, yB, zB);

// Create helper object
const helper = new THREE.Object3D();

// Center helper at vecA
helper.position.copy(vecA);

// Rotate helper towards vecB
helper.lookAt(vecB);

// Move helper perpendicularly along its own y-axis
const cylinderRadius = 27;
helper.translateY(cylinderRadius);

// Now you have your final position!
console.log(helper.position);

In the diagram below, the helper Object3D is shown as a red line only to give you a sense of its rotation and position, but in reality it is invisible unless you add a Mesh to it.

enter image description here

If you want to add/subtract 15 degrees from the perpendicular, you could just rotate the helper along its own x-axis before translateY()

const xAngle = THREE.MathUtils.degToRad(15);
helper.rotateX(xAngle);

const cylinderRadius = 27;
helper.translateY(cylinderRadius);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 e-motiv
Solution 2