@jscad/modeling
Version:
Constructive Solid Geometry (CSG) Library for JSCAD
285 lines (225 loc) • 8.21 kB
JavaScript
const test = require('ava')
const { geom2, geom3, path2 } = require('../../geometries')
const { measureVolume } = require('../../measurements')
const { sphere, cuboid, ellipsoid, torus } = require('../../primitives')
const { center } = require('../transforms/center')
const { hull } = require('./index')
const comparePolygonsAsPoints = require('../../../test/helpers/comparePolygonsAsPoints')
test('hull (single, geom2)', (t) => {
let geometry = geom2.create()
let obs = hull(geometry)
let pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(geometry))
t.is(pts.length, 0)
geometry = geom2.fromPoints([[5, 5], [-5, 5], [-5, -5], [5, -5]])
obs = hull(geometry)
pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(geometry))
t.is(pts.length, 4)
// convex C shape
geometry = geom2.fromPoints([
[5.00000, 8.66025],
[-5.00000, 8.66025],
[-10.00000, 0.00000],
[-5.00000, -8.66025],
[5.00000, -8.66025],
[6.00000, -6.92820],
[-2.00000, -6.92820],
[-6.00000, 0.00000],
[-2.00000, 6.92820],
[6.00000, 6.92820]
])
obs = hull(geometry)
pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(geometry))
t.is(pts.length, 7)
})
test('hull (multiple, overlapping, geom2)', (t) => {
const geometry1 = geom2.fromPoints([[5, 5], [-5, 5], [-5, -5], [5, -5]])
const geometry2 = geom2.fromPoints([[3, 3], [-3, 3], [-3, -3], [3, -3]])
const geometry3 = geom2.fromPoints([[6, 3], [-6, 3], [-6, -3], [6, -3]])
// convex C shape
const geometry4 = geom2.fromPoints([
[5.00000, 8.66025],
[-5.00000, 8.66025],
[-10.00000, 0.00000],
[-5.00000, -8.66025],
[5.00000, -8.66025],
[6.00000, -6.92820],
[-2.00000, -6.92820],
[-6.00000, 0.00000],
[-2.00000, 6.92820],
[6.00000, 6.92820]
])
// same
let obs = hull(geometry1, geometry1)
let pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(obs))
t.is(pts.length, 4)
// one inside another
obs = hull(geometry1, geometry2)
pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(obs))
t.is(pts.length, 4)
// one overlapping another
obs = hull(geometry1, geometry3)
pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(obs))
t.is(pts.length, 8)
obs = hull(geometry2, geometry4)
pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(obs))
t.is(pts.length, 7)
})
test('hull (multiple, various, geom2)', (t) => {
const geometry1 = geom2.fromPoints([[6, 6], [0, 6], [0, 0], [6, 0]])
const geometry2 = geom2.fromPoints([[6, 3], [-6, 3], [-6, -3], [6, -3]])
const geometry3 = geom2.fromPoints([[-10, -10], [0, -20], [10, -10]])
// convex C shape
const geometry4 = geom2.fromPoints([
[5.00000, 8.66025],
[-5.00000, 8.66025],
[-10.00000, 0.00000],
[-5.00000, -8.66025],
[5.00000, -8.66025],
[6.00000, -6.92820],
[-2.00000, -6.92820],
[-6.00000, 0.00000],
[-2.00000, 6.92820],
[6.00000, 6.92820]
])
const geometry5 = geom2.fromPoints([[-17, -17], [-23, -17], [-23, -23], [-17, -23]])
let obs = hull(geometry1, geometry2)
let pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(obs))
t.is(pts.length, 5)
obs = hull(geometry1, geometry3)
pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(obs))
t.is(pts.length, 5)
obs = hull(geometry2, geometry3)
pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(obs))
t.is(pts.length, 5)
obs = hull(geometry1, geometry2, geometry3)
pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(obs))
t.is(pts.length, 6)
obs = hull(geometry5, geometry4)
pts = geom2.toPoints(obs)
t.notThrows(() => geom2.validate(obs))
t.is(pts.length, 8)
})
test('hull (single, path2)', (t) => {
let geometry = path2.create()
let obs = hull(geometry)
let pts = path2.toPoints(obs)
t.notThrows(() => path2.validate(obs))
t.is(pts.length, 0)
geometry = path2.fromPoints({}, [[0, 0], [5, 0], [5, 10], [4, 1]])
obs = hull(geometry)
pts = path2.toPoints(obs)
t.notThrows(() => path2.validate(obs))
t.is(pts.length, 3)
})
test('hull (multiple, various, path2)', (t) => {
const geometry1 = path2.fromPoints({ closed: true }, [[6, 6], [0, 6], [0, 0], [6, 0]])
const geometry2 = path2.fromPoints({}, [[6, 3], [-6, 3], [-6, -3], [6, -3]])
const geometry3 = path2.fromPoints({ closed: true }, [[-10, -10], [0, -20], [10, -10]])
// convex C shape
const geometry4 = path2.fromPoints({}, [
[5.00000, 8.66025],
[-5.00000, 8.66025],
[-10.00000, 0.00000],
[-5.00000, -8.66025],
[5.00000, -8.66025],
[6.00000, -6.92820],
[-2.00000, -6.92820],
[-6.00000, 0.00000],
[-2.00000, 6.92820],
[6.00000, 6.92820]
])
const geometry5 = path2.fromPoints({ closed: true }, [[-17, -17], [-23, -17], [-23, -23], [-17, -23]])
let obs = hull(geometry1, geometry2)
let pts = path2.toPoints(obs)
t.notThrows(() => path2.validate(obs))
t.is(pts.length, 5)
obs = hull(geometry1, geometry3)
pts = path2.toPoints(obs)
t.notThrows(() => path2.validate(obs))
t.is(pts.length, 5)
obs = hull(geometry2, geometry3)
pts = path2.toPoints(obs)
t.notThrows(() => path2.validate(obs))
t.is(pts.length, 5)
obs = hull(geometry1, geometry2, geometry3)
pts = path2.toPoints(obs)
t.notThrows(() => path2.validate(obs))
t.is(pts.length, 6)
obs = hull(geometry5, geometry4)
pts = path2.toPoints(obs)
t.notThrows(() => path2.validate(obs))
t.is(pts.length, 8)
})
test('hull (single, geom3)', (t) => {
let geometry = geom3.create()
let obs = hull(geometry)
let pts = geom3.toPoints(obs)
t.notThrows(() => geom3.validate(obs))
t.is(pts.length, 0)
geometry = sphere({ radius: 2, segments: 8 })
obs = hull(geometry)
pts = geom3.toPoints(obs)
t.notThrows.skip(() => geom3.validate(obs))
t.is(pts.length, 32)
})
test('hull (multiple, geom3)', (t) => {
const geometry1 = cuboid({ size: [2, 2, 2] })
let obs = hull(geometry1, geometry1) // same
let pts = geom3.toPoints(obs)
let exp = [
[[-1, 1, -1], [-1, 1, 1], [1, 1, 1], [1, 1, -1]],
[[-1, 1, -1], [1, 1, -1], [1, -1, -1], [-1, -1, -1]],
[[-1, 1, -1], [-1, -1, -1], [-1, -1, 1], [-1, 1, 1]],
[[1, -1, 1], [1, -1, -1], [1, 1, -1], [1, 1, 1]],
[[1, -1, 1], [1, 1, 1], [-1, 1, 1], [-1, -1, 1]],
[[1, -1, 1], [-1, -1, 1], [-1, -1, -1], [1, -1, -1]]
]
t.notThrows(() => geom3.validate(obs))
t.is(pts.length, 6)
t.true(comparePolygonsAsPoints(pts, exp))
const geometry2 = center({ relativeTo: [5, 5, 5] }, cuboid({ size: [3, 3, 3] }))
obs = hull(geometry1, geometry2)
pts = geom3.toPoints(obs)
exp = [
[[1, -1, -1], [6.5, 3.5, 3.5], [6.5, 3.5, 6.5], [1, -1, 1]],
[[-1, -1, 1], [-1, -1, -1], [1, -1, -1], [1, -1, 1]],
[[-1, -1, 1], [1, -1, 1], [6.5, 3.5, 6.5], [3.5, 3.5, 6.5]],
[[-1, -1, 1], [3.5, 3.5, 6.5], [3.5, 6.5, 6.5], [-1, 1, 1]],
[[-1, 1, -1], [-1, 1, 1], [3.5, 6.5, 6.5], [3.5, 6.5, 3.5]],
[[-1, 1, -1], [-1, -1, -1], [-1, -1, 1], [-1, 1, 1]],
[[6.5, 6.5, 6.5], [6.5, 3.5, 6.5], [6.5, 3.5, 3.5], [6.5, 6.5, 3.5]],
[[6.5, 6.5, 6.5], [6.5, 6.5, 3.5], [3.5, 6.5, 3.5], [3.5, 6.5, 6.5]],
[[6.5, 6.5, 6.5], [3.5, 6.5, 6.5], [3.5, 3.5, 6.5], [6.5, 3.5, 6.5]],
[[1, 1, -1], [1, -1, -1], [-1, -1, -1], [-1, 1, -1]],
[[1, 1, -1], [-1, 1, -1], [3.5, 6.5, 3.5], [6.5, 6.5, 3.5]],
[[1, 1, -1], [6.5, 6.5, 3.5], [6.5, 3.5, 3.5], [1, -1, -1]]
]
t.notThrows(() => geom3.validate(obs))
t.is(pts.length, 12)
t.true(comparePolygonsAsPoints(pts, exp))
})
test('hull (multiple, overlapping, geom3)', (t) => {
const geometry1 = ellipsoid({ radius: [2, 2, 2], segments: 12 })
const geometry2 = center({ relativeTo: [3, -3, 3] }, ellipsoid({ radius: [3, 3, 3], segments: 12 }))
const geometry3 = center({ relativeTo: [-3, -3, -3] }, ellipsoid({ radius: [3, 3, 3], segments: 12 }))
const obs = hull(geometry1, geometry2, geometry3)
const pts = geom3.toPoints(obs)
t.notThrows(() => geom3.validate(obs))
t.is(pts.length, 92)
})
test('hull (single, unconvex, geom3)', (t) => {
const geometry = torus()
const obs = hull(geometry)
t.assert(measureVolume(obs) > measureVolume(geometry))
})