Skip to content

Commit

Permalink
fix(modeling): added fromNoisyPoints() to plane
Browse files Browse the repository at this point in the history
  • Loading branch information
z3dev committed Feb 13, 2024
1 parent 6c7be85 commit 1d9efb5
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 0 deletions.
6 changes: 6 additions & 0 deletions packages/modeling/src/maths/plane/fromNoisyPoints.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Plane from './type'
import Vec3 from '../vec3/type'

export default fromNoisyPoints

declare function fromNoisyPoints(out: Plane, ...vertices: Array<Vec3>): Plane
106 changes: 106 additions & 0 deletions packages/modeling/src/maths/plane/fromNoisyPoints.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
const vec3 = require('../vec3')
const fromNormalAndPoint = require('./fromNormalAndPoint')

/**
* Create a best-fit plane from the given noisy vertices.
*
* NOTE: There are two possible orientations for every plane.
* This function always produces positive orientations.
*
* See http://www.ilikebigbits.com for the original discussion
*
* @param {Plane} out - receiving plane
* @param {Array} vertices - list of vertices in any order or position
* @returns {Plane} out
* @alias module:modeling/maths/plane.fromNoisyPoints
*/
const fromNoisyPoints = (out, ...vertices) => {
out[0] = 0.0
out[1] = 0.0
out[2] = 0.0
out[3] = 0.0

// calculate the centroid of the vertices
// NOTE: out is the centriod
const n = vertices.length
vertices.forEach((v) => {
vec3.add(out, out, v)
})
vec3.scale(out, out, 1.0 / n)

// Calculate full 3x3 covariance matrix, excluding symmetries
let xx = 0.0
let xy = 0.0
let xz = 0.0
let yy = 0.0
let yz = 0.0
let zz = 0.0

const vn = vec3.create()
vertices.forEach((v) => {
// NOTE: out is the centriod
vec3.subtract(vn, v, out)
xx += vn[0] * vn[0]
xy += vn[0] * vn[1]
xz += vn[0] * vn[2]
yy += vn[1] * vn[1]
yz += vn[1] * vn[2]
zz += vn[2] * vn[2]
})

xx /= n
xy /= n
xz /= n
yy /= n
yz /= n
zz /= n

// Calculate the smallest Eigenvector of the covariance matrix
// which becomes the plane normal

vn[0] = 0.0
vn[1] = 0.0
vn[2] = 0.0

// weighted directional vector
const wdv = vec3.create()

// X axis
let det = yy * zz - yz * yz
wdv[0] = det
wdv[1] = xz * yz - xy * zz
wdv[2] = xy * yz - xz * yy

let weight = det * det
vec3.add(vn, vn, vec3.scale(wdv, wdv, weight))

// Y axis
det = xx * zz - xz * xz
wdv[0] = xz * yz - xy * zz
wdv[1] = det
wdv[2] = xy * xz - yz * xx

weight = det * det
if (vec3.dot(vn, wdv) < 0.0) {
weight = -weight
}
vec3.add(vn, vn, vec3.scale(wdv, wdv, weight))

// Z axis
det = xx * yy - xy * xy
wdv[0] = xy * yz - xz * yy
wdv[1] = xy * xz - yz * xx
wdv[2] = det

weight = det * det
if (vec3.dot(vn, wdv) < 0.0) {
weight = -weight
}
vec3.add(vn, vn, vec3.scale(wdv, wdv, weight))

// create the plane from normal and centriod
// NOTE: out is the centriod
return fromNormalAndPoint(out, vn, out)
}

module.exports = fromNoisyPoints
24 changes: 24 additions & 0 deletions packages/modeling/src/maths/plane/fromNoisyPoints.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const test = require('ava')
const { fromNoisyPoints, create } = require('./index')

const { compareVectors } = require('../../../test/helpers/index')

test('plane: fromNoisyPoints() should return a new plane with correct values', (t) => {
const obs1 = fromNoisyPoints(create(), [0, 0, 0], [1, 0, 0], [1, 1, 0])
t.true(compareVectors(obs1, [0, 0, 1, 0]))

const obs2 = fromNoisyPoints(obs1, [0, 6, 0], [0, 2, 2], [0, 6, 6])
t.true(compareVectors(obs2, [1, 0, 0, 0]))

// same vertices results in an invalid plane
const obs3 = fromNoisyPoints(obs1, [0, 6, 0], [0, 6, 0], [0, 6, 0])
t.true(compareVectors(obs3, [0 / 0, 0 / 0, 0 / 0, 0 / 0]))

// co-linear vertices
const obs4 = fromNoisyPoints(obs1, [0, 0, 0], [1, 0, 0], [2, 0, 0], [0, 1, 0])
t.true(compareVectors(obs4, [0, 0, 1, 0]))

// random vertices
const obs5 = fromNoisyPoints(obs1, [0, 0, 0], [5, 1, -2], [3, -2, 4], [1, 1, 0])
t.true(compareVectors(obs5, [0.08054818365229491, 0.8764542170444571, 0.47469990050062555, 0.4185833634679763]))
})
1 change: 1 addition & 0 deletions packages/modeling/src/maths/plane/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { default as equals } from './equals'
export { default as flip } from './flip'
export { default as fromNormalAndPoint } from './fromNormalAndPoint'
export { default as fromValues } from './fromValues'
export { default as fromNoisyPoints } from './fromNoisyPoints'
export { default as fromPoints } from './fromPoints'
export { default as fromPointsRandom } from './fromPointsRandom'
export { default as signedDistanceToPoint } from './signedDistanceToPoint'
Expand Down
1 change: 1 addition & 0 deletions packages/modeling/src/maths/plane/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ module.exports = {
* @function fromValues
*/
fromValues: require('../vec4/fromValues'),
fromNoisyPoints: require('./fromNoisyPoints'),
fromPoints: require('./fromPoints'),
fromPointsRandom: require('./fromPointsRandom'),
projectionOfPoint: require('./projectionOfPoint'),
Expand Down

0 comments on commit 1d9efb5

Please sign in to comment.