123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
- import { Vec3, Mat4, EPSILON } from '../../linear-algebra';
- import { PositionData } from '../common';
- import { OrderedSet } from '../../../mol-data/int';
- import { NumberArray, PickRequired } from '../../../mol-util/type-helpers';
- import { Box3D } from './box3d';
- import { Axes3D } from './axes3d';
- interface Sphere3D {
- center: Vec3,
- radius: number,
- extrema?: Vec3[]
- }
- function Sphere3D() {
- return Sphere3D.zero();
- }
- namespace Sphere3D {
- export function hasExtrema(sphere: Sphere3D): sphere is PickRequired<Sphere3D, 'extrema'> {
- return sphere.extrema !== undefined;
- }
- export function create(center: Vec3, radius: number): Sphere3D { return { center, radius }; }
- export function zero(): Sphere3D { return { center: Vec3(), radius: 0 }; }
- export function clone(a: Sphere3D): Sphere3D {
- const out = create(Vec3.clone(a.center), a.radius);
- if (hasExtrema(a)) out.extrema = a.extrema.map(e => Vec3.clone(e));
- return out;
- }
- export function set(out: Sphere3D, center: Vec3, radius: number) {
- Vec3.copy(out.center, center);
- out.radius = radius;
- return out;
- }
- export function copy(out: Sphere3D, a: Sphere3D) {
- Vec3.copy(out.center, a.center);
- out.radius = a.radius;
- if (hasExtrema(a)) setExtrema(out, a.extrema.map(e => Vec3.clone(e)));
- return out;
- }
- /** Note that `extrema` must not be reused elsewhere */
- export function setExtrema(out: Sphere3D, extrema: Vec3[]): Sphere3D {
- if (out.extrema !== undefined) {
- out.extrema.length = 0;
- out.extrema.push(...extrema);
- } else {
- out.extrema = extrema;
- }
- return out;
- }
- export function computeBounding(data: PositionData): Sphere3D {
- const { x, y, z, indices } = data;
- let cx = 0, cy = 0, cz = 0;
- let radiusSq = 0;
- const size = OrderedSet.size(indices);
- for (let t = 0; t < size; t++) {
- const i = OrderedSet.getAt(indices, t);
- cx += x[i];
- cy += y[i];
- cz += z[i];
- }
- if (size > 0) {
- cx /= size;
- cy /= size;
- cz /= size;
- }
- for (let t = 0; t < size; t++) {
- const i = OrderedSet.getAt(indices, t);
- const dx = x[i] - cx, dy = y[i] - cy, dz = z[i] - cz;
- const d = dx * dx + dy * dy + dz * dz;
- if (d > radiusSq) radiusSq = d;
- }
- return { center: Vec3.create(cx, cy, cz), radius: Math.sqrt(radiusSq) };
- }
- /** Transform sphere with a Mat4 */
- export function transform(out: Sphere3D, sphere: Sphere3D, m: Mat4): Sphere3D {
- Vec3.transformMat4(out.center, sphere.center, m);
- out.radius = sphere.radius * Mat4.getMaxScaleOnAxis(m);
- if (hasExtrema(sphere)) {
- setExtrema(out, sphere.extrema.map(e => Vec3.transformMat4(Vec3(), e, m)));
- }
- return out;
- }
- /** Translate sphere by Vec3 */
- export function translate(out: Sphere3D, sphere: Sphere3D, v: Vec3) {
- Vec3.add(out.center, sphere.center, v);
- if (hasExtrema(sphere)) {
- setExtrema(out, sphere.extrema.map(e => Vec3.add(Vec3(), e, v)));
- }
- return out;
- }
- export function toArray(s: Sphere3D, out: NumberArray, offset: number) {
- Vec3.toArray(s.center, out, offset);
- out[offset + 3] = s.radius;
- }
- export function fromArray(out: Sphere3D, array: NumberArray, offset: number) {
- Vec3.fromArray(out.center, array, offset);
- out.radius = array[offset + 3];
- return out;
- }
- export function fromBox3D(out: Sphere3D, box: Box3D) {
- Vec3.scale(out.center, Vec3.add(out.center, box.max, box.min), 0.5);
- out.radius = Vec3.distance(out.center, box.max);
- return out;
- }
- export function fromAxes3D(out: Sphere3D, axes: Axes3D) {
- Vec3.copy(out.center, axes.origin);
- out.radius = Math.max(Vec3.magnitude(axes.dirA), Vec3.magnitude(axes.dirB), Vec3.magnitude(axes.dirC));
- return out;
- }
- const tmpCenter = Vec3();
- /** Get a tight sphere around a transformed box */
- export function fromDimensionsAndTransform(out: Sphere3D, dimensions: Vec3, transform: Mat4) {
- const [x, y, z] = dimensions;
- const cpA = Vec3.create(0, 0, 0); Vec3.transformMat4(cpA, cpA, transform);
- const cpB = Vec3.create(x, y, z); Vec3.transformMat4(cpB, cpB, transform);
- const cpC = Vec3.create(x, 0, 0); Vec3.transformMat4(cpC, cpC, transform);
- const cpD = Vec3.create(0, y, z); Vec3.transformMat4(cpD, cpD, transform);
- const cpE = Vec3.create(0, 0, z); Vec3.transformMat4(cpE, cpE, transform);
- const cpF = Vec3.create(x, 0, z); Vec3.transformMat4(cpF, cpF, transform);
- const cpG = Vec3.create(x, y, 0); Vec3.transformMat4(cpG, cpG, transform);
- const cpH = Vec3.create(0, y, 0); Vec3.transformMat4(cpH, cpH, transform);
- Vec3.add(tmpCenter, cpA, cpB);
- Vec3.scale(tmpCenter, tmpCenter, 0.5);
- const d = Math.max(Vec3.distance(cpA, cpB), Vec3.distance(cpC, cpD));
- Sphere3D.set(out, tmpCenter, d / 2);
- Sphere3D.setExtrema(out, [cpA, cpB, cpC, cpD, cpE, cpF, cpG, cpH]);
- return out;
- }
- const tmpAddVec3 = Vec3();
- export function addVec3(out: Sphere3D, s: Sphere3D, v: Vec3) {
- const d = Vec3.distance(s.center, v);
- if (d < s.radius) return Sphere3D.copy(out, s);
- Vec3.sub(tmpAddVec3, s.center, v);
- Vec3.sub(tmpAddVec3, s.center, tmpAddVec3);
- Vec3.setMagnitude(tmpAddVec3, tmpAddVec3, s.radius);
- Vec3.scale(out.center, Vec3.add(tmpAddVec3, tmpAddVec3, v), 0.5);
- out.radius = Vec3.distance(out.center, v);
- return out;
- }
- /** Expand sphere radius by another sphere */
- export function expandBySphere(out: Sphere3D, sphere: Sphere3D, by: Sphere3D) {
- Vec3.copy(out.center, sphere.center);
- out.radius = Math.max(sphere.radius, Vec3.distance(sphere.center, by.center) + by.radius);
- if (hasExtrema(sphere) && hasExtrema(by)) {
- setExtrema(out, [
- ...sphere.extrema.map(e => Vec3.clone(e)),
- ...by.extrema.map(e => Vec3.clone(e))
- ]);
- }
- return out;
- }
- const tmpDir = Vec3();
- /** Expand sphere radius by delta */
- export function expand(out: Sphere3D, sphere: Sphere3D, delta: number): Sphere3D {
- Vec3.copy(out.center, sphere.center);
- out.radius = sphere.radius + delta;
- if (hasExtrema(sphere)) {
- setExtrema(out, sphere.extrema.map(e => {
- Vec3.sub(tmpDir, e, sphere.center);
- const dist = Vec3.distance(sphere.center, e);
- Vec3.normalize(tmpDir, tmpDir);
- return Vec3.scaleAndAdd(Vec3(), sphere.center, tmpDir, dist + delta);
- }));
- }
- return out;
- }
- /**
- * Returns whether or not the spheres have exactly the same center and radius (when compared with ===)
- */
- export function exactEquals(a: Sphere3D, b: Sphere3D) {
- return a.radius === b.radius && Vec3.exactEquals(a.center, b.center);
- }
- /**
- * Returns whether or not the spheres have approximately the same center and radius.
- */
- export function equals(a: Sphere3D, b: Sphere3D) {
- const ar = a.radius;
- const br = b.radius;
- return (Math.abs(ar - br) <= EPSILON * Math.max(1.0, Math.abs(ar), Math.abs(br)) &&
- Vec3.equals(a.center, b.center));
- }
- /**
- * Check if `a` includes `b`, use `extrema` of `b` when available
- */
- export function includes(a: Sphere3D, b: Sphere3D) {
- if (hasExtrema(b)) {
- for (const e of b.extrema) {
- if (Vec3.distance(a.center, e) > a.radius) return false;
- }
- return true;
- } else {
- return Vec3.distance(a.center, b.center) + b.radius <= a.radius;
- }
- }
- /** Check if `a` and `b` are overlapping */
- export function overlaps(a: Sphere3D, b: Sphere3D) {
- return Vec3.distance(a.center, b.center) <= a.radius + b.radius;
- }
- /** Get the signed distance of `a` and `b` */
- export function distance(a: Sphere3D, b: Sphere3D) {
- return Vec3.distance(a.center, b.center) - a.radius + b.radius;
- }
- }
- export { Sphere3D };
|