sphere3d.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { Vec3, Mat4, EPSILON } from '../../linear-algebra';
  8. import { PositionData } from '../common';
  9. import { OrderedSet } from '../../../mol-data/int';
  10. import { NumberArray, PickRequired } from '../../../mol-util/type-helpers';
  11. import { Box3D } from './box3d';
  12. import { Axes3D } from './axes3d';
  13. interface Sphere3D {
  14. center: Vec3,
  15. radius: number,
  16. extrema?: Vec3[]
  17. }
  18. function Sphere3D() {
  19. return Sphere3D.zero();
  20. }
  21. namespace Sphere3D {
  22. export function hasExtrema(sphere: Sphere3D): sphere is PickRequired<Sphere3D, 'extrema'> {
  23. return sphere.extrema !== undefined;
  24. }
  25. export function create(center: Vec3, radius: number): Sphere3D { return { center, radius }; }
  26. export function zero(): Sphere3D { return { center: Vec3(), radius: 0 }; }
  27. export function clone(a: Sphere3D): Sphere3D {
  28. const out = create(Vec3.clone(a.center), a.radius);
  29. if (hasExtrema(a)) out.extrema = a.extrema.map(e => Vec3.clone(e));
  30. return out;
  31. }
  32. export function set(out: Sphere3D, center: Vec3, radius: number) {
  33. Vec3.copy(out.center, center);
  34. out.radius = radius;
  35. return out;
  36. }
  37. export function copy(out: Sphere3D, a: Sphere3D) {
  38. Vec3.copy(out.center, a.center);
  39. out.radius = a.radius;
  40. if (hasExtrema(a)) setExtrema(out, a.extrema.map(e => Vec3.clone(e)));
  41. return out;
  42. }
  43. /** Note that `extrema` must not be reused elsewhere */
  44. export function setExtrema(out: Sphere3D, extrema: Vec3[]): Sphere3D {
  45. if (out.extrema !== undefined) {
  46. out.extrema.length = 0;
  47. out.extrema.push(...extrema);
  48. } else {
  49. out.extrema = extrema;
  50. }
  51. return out;
  52. }
  53. export function computeBounding(data: PositionData): Sphere3D {
  54. const { x, y, z, indices } = data;
  55. let cx = 0, cy = 0, cz = 0;
  56. let radiusSq = 0;
  57. const size = OrderedSet.size(indices);
  58. for (let t = 0; t < size; t++) {
  59. const i = OrderedSet.getAt(indices, t);
  60. cx += x[i];
  61. cy += y[i];
  62. cz += z[i];
  63. }
  64. if (size > 0) {
  65. cx /= size;
  66. cy /= size;
  67. cz /= size;
  68. }
  69. for (let t = 0; t < size; t++) {
  70. const i = OrderedSet.getAt(indices, t);
  71. const dx = x[i] - cx, dy = y[i] - cy, dz = z[i] - cz;
  72. const d = dx * dx + dy * dy + dz * dz;
  73. if (d > radiusSq) radiusSq = d;
  74. }
  75. return { center: Vec3.create(cx, cy, cz), radius: Math.sqrt(radiusSq) };
  76. }
  77. /** Transform sphere with a Mat4 */
  78. export function transform(out: Sphere3D, sphere: Sphere3D, m: Mat4): Sphere3D {
  79. Vec3.transformMat4(out.center, sphere.center, m);
  80. out.radius = sphere.radius * Mat4.getMaxScaleOnAxis(m);
  81. if (hasExtrema(sphere)) {
  82. setExtrema(out, sphere.extrema.map(e => Vec3.transformMat4(Vec3(), e, m)));
  83. }
  84. return out;
  85. }
  86. /** Translate sphere by Vec3 */
  87. export function translate(out: Sphere3D, sphere: Sphere3D, v: Vec3) {
  88. Vec3.add(out.center, sphere.center, v);
  89. if (hasExtrema(sphere)) {
  90. setExtrema(out, sphere.extrema.map(e => Vec3.add(Vec3(), e, v)));
  91. }
  92. return out;
  93. }
  94. export function toArray(s: Sphere3D, out: NumberArray, offset: number) {
  95. Vec3.toArray(s.center, out, offset);
  96. out[offset + 3] = s.radius;
  97. }
  98. export function fromArray(out: Sphere3D, array: NumberArray, offset: number) {
  99. Vec3.fromArray(out.center, array, offset);
  100. out.radius = array[offset + 3];
  101. return out;
  102. }
  103. export function fromBox3D(out: Sphere3D, box: Box3D) {
  104. Vec3.scale(out.center, Vec3.add(out.center, box.max, box.min), 0.5);
  105. out.radius = Vec3.distance(out.center, box.max);
  106. return out;
  107. }
  108. export function fromAxes3D(out: Sphere3D, axes: Axes3D) {
  109. Vec3.copy(out.center, axes.origin);
  110. out.radius = Math.max(Vec3.magnitude(axes.dirA), Vec3.magnitude(axes.dirB), Vec3.magnitude(axes.dirC));
  111. return out;
  112. }
  113. const tmpCenter = Vec3();
  114. /** Get a tight sphere around a transformed box */
  115. export function fromDimensionsAndTransform(out: Sphere3D, dimensions: Vec3, transform: Mat4) {
  116. const [x, y, z] = dimensions;
  117. const cpA = Vec3.create(0, 0, 0); Vec3.transformMat4(cpA, cpA, transform);
  118. const cpB = Vec3.create(x, y, z); Vec3.transformMat4(cpB, cpB, transform);
  119. const cpC = Vec3.create(x, 0, 0); Vec3.transformMat4(cpC, cpC, transform);
  120. const cpD = Vec3.create(0, y, z); Vec3.transformMat4(cpD, cpD, transform);
  121. const cpE = Vec3.create(0, 0, z); Vec3.transformMat4(cpE, cpE, transform);
  122. const cpF = Vec3.create(x, 0, z); Vec3.transformMat4(cpF, cpF, transform);
  123. const cpG = Vec3.create(x, y, 0); Vec3.transformMat4(cpG, cpG, transform);
  124. const cpH = Vec3.create(0, y, 0); Vec3.transformMat4(cpH, cpH, transform);
  125. Vec3.add(tmpCenter, cpA, cpB);
  126. Vec3.scale(tmpCenter, tmpCenter, 0.5);
  127. const d = Math.max(Vec3.distance(cpA, cpB), Vec3.distance(cpC, cpD));
  128. Sphere3D.set(out, tmpCenter, d / 2);
  129. Sphere3D.setExtrema(out, [cpA, cpB, cpC, cpD, cpE, cpF, cpG, cpH]);
  130. return out;
  131. }
  132. const tmpAddVec3 = Vec3();
  133. export function addVec3(out: Sphere3D, s: Sphere3D, v: Vec3) {
  134. const d = Vec3.distance(s.center, v);
  135. if (d < s.radius) return Sphere3D.copy(out, s);
  136. Vec3.sub(tmpAddVec3, s.center, v);
  137. Vec3.sub(tmpAddVec3, s.center, tmpAddVec3);
  138. Vec3.setMagnitude(tmpAddVec3, tmpAddVec3, s.radius);
  139. Vec3.scale(out.center, Vec3.add(tmpAddVec3, tmpAddVec3, v), 0.5);
  140. out.radius = Vec3.distance(out.center, v);
  141. return out;
  142. }
  143. /** Expand sphere radius by another sphere */
  144. export function expandBySphere(out: Sphere3D, sphere: Sphere3D, by: Sphere3D) {
  145. Vec3.copy(out.center, sphere.center);
  146. out.radius = Math.max(sphere.radius, Vec3.distance(sphere.center, by.center) + by.radius);
  147. if (hasExtrema(sphere) && hasExtrema(by)) {
  148. setExtrema(out, [
  149. ...sphere.extrema.map(e => Vec3.clone(e)),
  150. ...by.extrema.map(e => Vec3.clone(e))
  151. ]);
  152. }
  153. return out;
  154. }
  155. const tmpDir = Vec3();
  156. /** Expand sphere radius by delta */
  157. export function expand(out: Sphere3D, sphere: Sphere3D, delta: number): Sphere3D {
  158. Vec3.copy(out.center, sphere.center);
  159. out.radius = sphere.radius + delta;
  160. if (hasExtrema(sphere)) {
  161. setExtrema(out, sphere.extrema.map(e => {
  162. Vec3.sub(tmpDir, e, sphere.center);
  163. const dist = Vec3.distance(sphere.center, e);
  164. Vec3.normalize(tmpDir, tmpDir);
  165. return Vec3.scaleAndAdd(Vec3(), sphere.center, tmpDir, dist + delta);
  166. }));
  167. }
  168. return out;
  169. }
  170. /**
  171. * Returns whether or not the spheres have exactly the same center and radius (when compared with ===)
  172. */
  173. export function exactEquals(a: Sphere3D, b: Sphere3D) {
  174. return a.radius === b.radius && Vec3.exactEquals(a.center, b.center);
  175. }
  176. /**
  177. * Returns whether or not the spheres have approximately the same center and radius.
  178. */
  179. export function equals(a: Sphere3D, b: Sphere3D) {
  180. const ar = a.radius;
  181. const br = b.radius;
  182. return (Math.abs(ar - br) <= EPSILON * Math.max(1.0, Math.abs(ar), Math.abs(br)) &&
  183. Vec3.equals(a.center, b.center));
  184. }
  185. /**
  186. * Check if `a` includes `b`, use `extrema` of `b` when available
  187. */
  188. export function includes(a: Sphere3D, b: Sphere3D) {
  189. if (hasExtrema(b)) {
  190. for (const e of b.extrema) {
  191. if (Vec3.distance(a.center, e) > a.radius) return false;
  192. }
  193. return true;
  194. } else {
  195. return Vec3.distance(a.center, b.center) + b.radius <= a.radius;
  196. }
  197. }
  198. /** Check if `a` and `b` are overlapping */
  199. export function overlaps(a: Sphere3D, b: Sphere3D) {
  200. return Vec3.distance(a.center, b.center) <= a.radius + b.radius;
  201. }
  202. /** Get the signed distance of `a` and `b` */
  203. export function distance(a: Sphere3D, b: Sphere3D) {
  204. return Vec3.distance(a.center, b.center) - a.radius + b.radius;
  205. }
  206. }
  207. export { Sphere3D };