Apbln
Apbln

Reputation: 177

How to determine whether a point is in between two others?

I made a little function that doesn't work quite right...

function isPointbetweenTwoOthers (pA, pB, pToCheck) {
    var pApB = new THREE.Vector3();
    pApB.subVectors(pA,pB).normalize();

    var pBpA = new THREE.Vector3();
    pBpA.subVectors(pB,pA).normalize();

    var pA_pToCheck = new THREE.Vector3();
    pA_pToCheck.subVectors(pA,pToCheck).normalize();

    var pB_pToCheck = new THREE.Vector3();
    pB_pToCheck.subVectors(pB,pToCheck).normalize();

    if(pA_pToCheck.dot(pApB) <0 || pB_pToCheck.dot(pBpA) <0)
    return false
    else return true
}

enter image description here What am I doing wrong? According to the output of my function this left above point is between the two on the right

@Pete the new function:

function isPointBetweenTwoOthers (pA, pB, pToCheck) {
    var pApB = new THREE.Vector3();
    pApB.subVectors(pA,pB)
    var pApB_length = pApB.length()

    var pBpA = new THREE.Vector3();
    pBpA.subVectors(pB,pA)
    var pBpA_length = pBpA.length()
    var pA_pToCheck = new THREE.Vector3();
    pA_pToCheck.subVectors(pA,pToCheck)

    var pB_pToCheck = new THREE.Vector3();
    pB_pToCheck.subVectors(pB,pToCheck)

    var pToCheck_pA = new THREE.Vector3();
    pToCheck_pA.subVectors(pToCheck,pA)
    var pToCheck_pA_length = pToCheck_pA.length()

    if(pToCheck_pA_length > pBpA_length)
    {return false}
        else if(pToCheck_pA.dot(pBpA) > 0.999999 *pToCheck_pA.x*pBpA.x + pToCheck_pA.y*pBpA.y + pToCheck_pA.z*pBpA.z) 
        {return true}
        else {return false}
}

Now (the dark sphere in the pic below is point C) it outputs this case as true as long as I add the 0.999999 . Without it, it works fine in this case... enter image description here but in another case without the 0.9999, it tells me C is not between A & B using the following points...

var t1 = new THREE.Vector3(1,1,1); //A
var t2 = new THREE.Vector3(-1,1,1); //B
var t3 = new THREE.Vector3(-0.3999999999999999,1,1);  //C

enter image description here

Upvotes: 1

Views: 848

Answers (2)

Pete
Pete

Reputation: 421

To check, whether C is between A and B, calculate e.g. the vector differences u=subVectors(pB,pA) and v=subVectors(pC,pA). Calculate the lengths of both vectors lu and lv, respectively. If lv>lu, return false, as C is further away from A than B is. If this is not the case, check whether the dot product of u and v equals lu*lv (or maybe if it is larger than 0.9999*lu*lv to have some numerical tolerance) and return true in this case, false otherwise. Explanation: if C is not further away from A than B, and the vectors from A to B and from A to C are parallel (but not anti-parallel), the dot product is lu*lv ( cos(0 degrees)=1 , while cos(180 degrees, anti-parallel case)=-1 ) and C lies between A and B.

Upvotes: 3

Rabbid76
Rabbid76

Reputation: 211278

If you want to calculate the vector from a point pA to a point pB then you've to subtract pA form pB (pB-pA) rather than pB from pA (pA-pB).
Note, .subVectors( a : Vector3, b : Vector3 ) calculates a-b.


Short answer:

Verify if the distance between pA and pToCheck and the distance between pB and pToCheck is less or equal than the distance between pA and pB.
And verify if the angle between the vector form pA to pToCheck respectively pB to pToCheckand the vector from pA to pB is near to 0:

This can be done by .distanceTo respectively by .angleTo:

function isPointbetweenTwoOthers (pA, pB, pToCheck) {

    let distAtoB = pA.distanceTo(pB);
    let distAtoC = pA.distanceTo(pToCheck);
    let distBtoC = pB.distanceTo(pToCheck);
    if (distAtoC > distAtoB || distBtoC > distAtoB)
        return false;

    let vAtoB = pB.clone().sub(pA);
    let vBtoA = pA.clone().sub(pB);
    let vAtoC = pToCheck.clone().sub(pA);
    let vBtoC = pToCheck.clone().sub(pB);

    let angleAC = vAtoB.angleTo(vAtoC);
    let angleBC = vBtoA.angleTo(vBtoC);
    let epsilon = 0.0017; // 0.1° in radians 
    if (Math.abs(angleAC) > epsilon || Math.abs(angleBC) > epsilon)
        return false;

    return true;
}

Long answer:

The Dot product between a vector A and a vector B is equal to the product of the magnitude (length) of the vectors and the cosine of the angle between the vectors.

A dot B == |A| * |B| * cos(alpha)

If the 2 vectors are Unit vectors (normalized), then the dot product is equal to the cosine of the angle between the 2 vectors:

uA = normalize(A)
uB = normalize(B)
uA dot uB == cos(alpha)

The cosine of an angle is grater than 0 if the angle alpha is in greater -90° and less than 90°, It is 0 for 90° or -90° and less than 0 else.
The cosine is 1.0 if the angle is 0° and -1.0 if the angle is 180°.

If you just want to verify if pC is on a line from between pA and pB, then you've to verify if the angle between vectors is 0° (or 180°) respectively the cosine of the angle is 1.0 (or -1.0).
Because of the limited accuracy of the operations you can't directly compare to 1.0. You've to verify if the cosine is grater than a value near to 1.0.
The vectors have to be normalized and use an epsilon of ~0,0016 witch corresponds to the cosine of (0.1°):

     pA      pC      pB
       x-------x-------x

vAtoB  |-------------->|
vAtoC  |------>|  
vBtoC          |<------|
nvAtoB = normalize(pB-pA)
nvAtoC = normalize(pC-pA)
nvBtoC = normalize(pC-pB)

isOn = dot(nvAtoB, nvAtoC) > (1-epsilon) AND dot(nvAtoB, nvBtoC) < -(1-epsilon)

The function has to be:

function isPointbetweenTwoOthers (pA, pB, pToCheck) {
    var nvAtoB = new THREE.Vector3();
    nvAtoB.subVectors(pB, pA).normalize();

    var nvAtoC = new THREE.Vector3();
    nvAtoC.subVectors(pToCheck, pA).normalize();

    var nvBtoC = new THREE.Vector3();
    nvBtoC.subVectors(pToCheck, pB).normalize();

    let epsilon = 0.0016;
    let cos90epsi = 1.0 - epsilon;
    return nvAtoB.dot(nvAtoC) > cos90epsi && nvAtoB.dot(nvBtoC) < -cos90epsi;
}

A different approach is to find the nearest point pX to pC on the line from pA to pB. If pX is between pA and pB and the distance from pX to pC is below a certain threshold, then pC is between pA to pB. Since pX is on the line defined by pA to pB, it is sufficient to compare the cosine of the angle to 0.0.

If you've a line defined by a point O on a unit direction vector D, then the intersection point X, can be calculated by shifting the point O along the line (D) by the distance d:

X = O + D * d

So the formula for the intersection point is:

O ... any point on the line
D ... unit vector which points in the direction of the line
P ... the "Point"

X = O + D * dot(P-O, D); 

intersection line point

The calculation by points on the line A, B and the point P is:

D = normalize(B-A);
X = A + D * dot(P-A, D);

Fortunately THREE.js provides .projectOnVector(), which does the calculation of D * dot(P-A, D). See also Vector projection:

function isPointbetweenTwoOthers (pA, pB, pToCheck) {

    let vAtoB = pB.clone().sub(pA);
    let vAtoC = pToCheck.clone().sub(pA);
    let vBtoC = pToCheck.clone().sub(pB);

    // fast exit, C can't be between A and B
    if (nvAtoB.dot(nvAtoC) < 0.0 || nvAtoB.dot(nvBtoC) > 0.0)
       return false;

    // nearest point X to C on the line A, B
    let X = pA.clone().add( vAtoC.clone().projectOnVector( vAtoB ) );

    // distance between X and C
    let distXtoC = X.distanceTo( C );

    let threshold = 1.0; // ???
    return distXtoC < threshold;
}

This can be simplified further by the Cross product. The magnitude of the cross product of 2 vectors is equal to the area of the parallelogram that the vectors span.
This means if the cross product is near to 0.0, then the vectors are parallel and pC is on the line from pA to pB:

function isPointbetweenTwoOthers (pA, pB, pToCheck) {

    let vAtoB = pB.clone().sub(pA);
    let vAtoC = pToCheck.clone().sub(pA);
    let vBtoC = pToCheck.clone().sub(pB);

    // fast exit, C can't be between A and B
    if (nvAtoB.dot(nvAtoC) < 0.0 || nvAtoB.dot(nvBtoC) > 0.0)
       return false;

    // cross product and area spawned by the normalized vectors B-A and C-A
    let nv = vAtoB.normalize().cross(vAtoC.normalize());
    let area = nv.length();

    let epsilon = 0.0016; // ???
    return Math.abs(area) < epsilon;
}

Upvotes: 2

Related Questions