src/ccw.js
import {EPSILON} from './utils'
import Point from './Point'
// noinspection FunctionTooLongJS
/** ccw allows to know if traveling from P0 to P1 to P2 we turn counterclockwise
* assuming we are in cartesian space where X is directed to right and Y axis goes up
* and assuming the 3 points are distinct points
* if any points are identical ccw wil trow an error
* from p. 350 of Book : Algorithms in C++ by Robert Sedgewick Addison-Wesley ISBN 0-201-51059-6
* here are some examples :
* Point.ccw(P(0,0), P(2,0), P(2,3)) // should return this Object :
* {'counterClockwise': true, 'allPointsAreColinear': false, 'value': 1}
*
* Point.ccw(PP(0,0), P(2,0), P(2,-3)) // should return this Object :
* {'counterClockwise': false, 'allPointsAreColinear': false, 'value': -1}
*
* let {counterClockwise, allPointsAreColinear, value} = Point.ccw(Point(0, 0.3), Point(1, 0.6), Point(2, 0.9))
* will give you 'counterClockwise': true, 'allPointsAreColinear': true, 'value': 1
* have a look on my unit test in Point.spec.js for more examples
* @param {Point} P0
* @param {Point} P1
* @param {Point} P2
* @return {Object} 1 when turn is counterclockwise, -1 if not, and 0 when P2 colinear in segment between P0 and P1
*/
export const ccw = function (P0, P1, P2) {
if ((P0 instanceof Point) && (P1 instanceof Point) && (P2 instanceof Point)) {
let result = { counterClockwise: null, allPointsAreColinear: null, value: null }
const dx1 = P1.x - P0.x
const dx2 = P2.x - P0.x
const dy1 = P1.y - P0.y
const dy2 = P2.y - P0.y
console.log(`### IN Point.ccw(P0 = ${P0.toString()}, P1 = ${P1.toString()}, P2 = ${P2.toString()})`)
console.log(`dx1 = ${dx1}, dx2 = ${dx2}, dy1 = ${dy1}, dy2 = ${dy2}`)
if ((Math.abs(dx1) <= EPSILON) && (Math.abs(dy1) <= EPSILON)) {
console.log('ERROR ==> P0 and P1 are identical !')
throw new Error('PointCcwPointIdenticalException ==> P0 and P1 are identical')
}
if ((Math.abs(dx2) <= EPSILON) && (Math.abs(dy2) <= EPSILON)) {
console.log('ERROR ==> P0 and P2 are identical !')
throw new Error('PointCcwPointIdenticalException ==> P0 and P2 are identical')
}
if (P1.equal(P2)) {
console.log('ERROR ==> P1 and P2 are identical !')
throw new Error('PointCcwPointIdenticalException ==> P1 and P2 are identical')
}
const CPa = dx1 * dy2 // first term of "CrossProduct" or signed magnitude of 3d cross product vector with z=0
const CPb = dy1 * dx2 // second term of CrossProduct or signed magnitude
console.log(`Cpa = (dx1 * dy2) = ${CPa}, CPb = dy1 * dx2 = ${CPb}`)
if (Math.abs(CPa - CPb) <= EPSILON) { // colinear cases (epsilon test is here to handle float errors)
console.log(`==> ALL 3 POINTS ARE COLINEAR because = (Cpa - CPb) <= EPSILON `)
result.allPointsAreColinear = true
if (((dx1 * dx2) < 0) || ((dy1 * dy2) < 0)) {
result.counterClockwise = false
result.value = -1
console.log(`==> And P0 is between P1->P2`)
} else {
if ((dx1 * dx1 + dy1 * dy1) < (dx2 * dx2 + dy2 * dy2)) {
result.counterClockwise = true
result.value = 1
console.log(`==> And P1 is between P0->P2`)
} else {
result.counterClockwise = false
result.value = 0
console.log(`==> And P2 is between P0->P1`)
}
}
} else {
result.allPointsAreColinear = false
if (CPa > CPb) {
result.counterClockwise = true
result.value = 1
console.log(`=> From P0->P1->P2 we turn counter-clockwise`)
} else {
result.counterClockwise = false
result.value = -1
console.log(`=> From P0->P1->P2 we turn clockwise`)
}
}
return result
} else {
console.log('ERROR ==> P0,P1 and P2 SHOULD be of type Point class !')
throw new TypeError('Point.ccw(P0, P1, P2) expects all parameters to be from Point class')
}
}