Home Reference Source

src/volume/sdf/SuperPrimitive.js

  1. import { Box3, Vector2, Vector3, Vector4 } from "three";
  2. import { SignedDistanceFunction } from "./SignedDistanceFunction";
  3. import { SDFType } from "./SDFType";
  4.  
  5. /**
  6. * The super primitive.
  7. *
  8. * A function that is able to represent a wide range of conic/rectangular-radial
  9. * primitives of genus 0 and 1: (round) box, sphere, cylinder, capped cone,
  10. * torus, capsule, pellet, pipe, etc.
  11. *
  12. * Reference:
  13. * https://www.shadertoy.com/view/MsVGWG
  14. */
  15.  
  16. export class SuperPrimitive extends SignedDistanceFunction {
  17.  
  18. /**
  19. * Constructs a new super primitive.
  20. *
  21. * See {@link SuperPrimitivePreset} for a list of default configurations.
  22. *
  23. * @param {Object} parameters - The parameters.
  24. * @param {Array} parameters.s - The size and genus weight [x, y, z, w].
  25. * @param {Array} parameters.r - The corner radii [x, y, z].
  26. * @param {Number} [material] - A material index.
  27. * @example const cube = SuperPrimitive.create(SuperPrimitivePreset.CUBE);
  28. */
  29.  
  30. constructor(parameters = {}, material) {
  31.  
  32. super(SDFType.SUPER_PRIMITIVE, material);
  33.  
  34. /**
  35. * The base size. The W-component affects the genus of the primitive.
  36. *
  37. * @type {Vector4}
  38. * @private
  39. */
  40.  
  41. this.s0 = new Vector4(...parameters.s);
  42.  
  43. /**
  44. * The base corner radii.
  45. *
  46. * @type {Vector3}
  47. * @private
  48. */
  49.  
  50. this.r0 = new Vector3(...parameters.r);
  51.  
  52. /**
  53. * The size, adjusted for further calculations.
  54. *
  55. * @type {Vector4}
  56. * @private
  57. */
  58.  
  59. this.s = new Vector4();
  60.  
  61. /**
  62. * The corner radii, adjusted for further calculations.
  63. *
  64. * @type {Vector3}
  65. * @private
  66. */
  67.  
  68. this.r = new Vector3();
  69.  
  70. /**
  71. * Precomputed corner rounding constants.
  72. *
  73. * @type {Vector2}
  74. * @private
  75. */
  76.  
  77. this.ba = new Vector2();
  78.  
  79. /**
  80. * The bottom radius offset.
  81. *
  82. * @type {Number}
  83. * @private
  84. */
  85.  
  86. this.offset = 0;
  87.  
  88. // Calculate constants ahead of time.
  89. this.precompute();
  90.  
  91. }
  92.  
  93. /**
  94. * Sets the size and genus weight.
  95. *
  96. * @param {Number} x - X.
  97. * @param {Number} y - Y.
  98. * @param {Number} z - Z.
  99. * @param {Number} w - W.
  100. * @return {SuperPrimitive} This instance.
  101. */
  102.  
  103. setSize(x, y, z, w) {
  104.  
  105. this.s0.set(x, y, z, w);
  106.  
  107. return this.precompute();
  108.  
  109. }
  110.  
  111. /**
  112. * Sets the corner radii.
  113. *
  114. * @param {Number} x - X.
  115. * @param {Number} y - Y.
  116. * @param {Number} z - Z.
  117. * @return {SuperPrimitive} This instance.
  118. */
  119.  
  120. setRadii(x, y, z) {
  121.  
  122. this.r0.set(x, y, z);
  123.  
  124. return this.precompute();
  125.  
  126. }
  127.  
  128. /**
  129. * Precomputes corner rounding factors.
  130. *
  131. * @private
  132. * @return {SuperPrimitive} This instance.
  133. */
  134.  
  135. precompute() {
  136.  
  137. const s = this.s.copy(this.s0);
  138. const r = this.r.copy(this.r0);
  139. const ba = this.ba;
  140.  
  141. s.x -= r.x;
  142. s.y -= r.x;
  143.  
  144. r.x -= s.w;
  145. s.w -= r.y;
  146.  
  147. s.z -= r.y;
  148.  
  149. this.offset = -2.0 * s.z;
  150.  
  151. ba.set(r.z, this.offset);
  152. const divisor = ba.dot(ba);
  153.  
  154. if(divisor === 0.0) {
  155.  
  156. // Y must not be 0 to prevent bad values for Z = 0 in the last term (*).
  157. ba.set(0.0, -1.0);
  158.  
  159. } else {
  160.  
  161. ba.divideScalar(divisor);
  162.  
  163. }
  164.  
  165. return this;
  166.  
  167. }
  168.  
  169. /**
  170. * Calculates the bounding box of this SDF.
  171. *
  172. * @return {Box3} The bounding box.
  173. */
  174.  
  175. computeBoundingBox() {
  176.  
  177. const s = this.s0;
  178. const boundingBox = new Box3();
  179.  
  180. boundingBox.min.x = Math.min(-s.x, -1.0);
  181. boundingBox.min.y = Math.min(-s.y, -1.0);
  182. boundingBox.min.z = Math.min(-s.z, -1.0);
  183.  
  184. boundingBox.max.x = Math.max(s.x, 1.0);
  185. boundingBox.max.y = Math.max(s.y, 1.0);
  186. boundingBox.max.z = Math.max(s.z, 1.0);
  187.  
  188. boundingBox.applyMatrix4(this.getTransformation());
  189.  
  190. return boundingBox;
  191.  
  192. }
  193.  
  194. /**
  195. * Samples the volume's density at the given point in space.
  196. *
  197. * @param {Vector3} position - A position.
  198. * @return {Number} The euclidean distance to the surface.
  199. */
  200.  
  201. sample(position) {
  202.  
  203. position.applyMatrix4(this.inverseTransformation);
  204.  
  205. const s = this.s;
  206. const r = this.r;
  207. const ba = this.ba;
  208.  
  209. const dx = Math.abs(position.x) - s.x;
  210. const dy = Math.abs(position.y) - s.y;
  211. const dz = Math.abs(position.z) - s.z;
  212.  
  213. const mx0 = Math.max(dx, 0.0);
  214. const my0 = Math.max(dy, 0.0);
  215. const l0 = Math.sqrt(mx0 * mx0 + my0 * my0);
  216.  
  217. const p = position.z - s.z;
  218. const q = Math.abs(l0 + Math.min(0.0, Math.max(dx, dy)) - r.x) - s.w;
  219.  
  220. const c = Math.min(Math.max(q * ba.x + p * ba.y, 0.0), 1.0);
  221. const diagX = q - r.z * c;
  222. const diagY = p - this.offset * c;
  223.  
  224. const hx0 = Math.max(q - r.z, 0.0);
  225. const hy0 = position.z + s.z;
  226. const hx1 = Math.max(q, 0.0);
  227. // hy1 = p;
  228.  
  229. const diagSq = diagX * diagX + diagY * diagY;
  230. const h0Sq = hx0 * hx0 + hy0 * hy0;
  231. const h1Sq = hx1 * hx1 + p * p;
  232. const paBa = q * -ba.y + p * ba.x;
  233.  
  234. const l1 = Math.sqrt(Math.min(diagSq, Math.min(h0Sq, h1Sq)));
  235.  
  236. // (*) paBa must not be 0: if dz is also 0, the result will be wrong.
  237. return l1 * Math.sign(Math.max(paBa, dz)) - r.y;
  238.  
  239. }
  240.  
  241. /**
  242. * Serialises this SDF.
  243. *
  244. * @param {Boolean} [deflate=false] - Whether the data should be compressed if possible.
  245. * @return {Object} The serialised data.
  246. */
  247.  
  248. serialize(deflate = false) {
  249.  
  250. const result = super.serialize();
  251.  
  252. result.parameters = {
  253. s: this.s0.toArray(),
  254. r: this.r0.toArray()
  255. };
  256.  
  257. return result;
  258.  
  259. }
  260.  
  261. /**
  262. * Creates a new primitive using the specified preset.
  263. *
  264. * @param {SuperPrimitivePreset} preset - The super primitive preset.
  265. */
  266.  
  267. static create(preset) {
  268.  
  269. const parameters = superPrimitivePresets[preset];
  270.  
  271. return new SuperPrimitive({
  272. s: parameters[0],
  273. r: parameters[1]
  274. });
  275.  
  276. }
  277.  
  278. }
  279.  
  280. /**
  281. * A list of parameter presets.
  282. *
  283. * @type {Array<Float32Array[]>}
  284. * @private
  285. */
  286.  
  287. const superPrimitivePresets = [
  288.  
  289. // Cube.
  290. [
  291. new Float32Array([1.0, 1.0, 1.0, 1.0]),
  292. new Float32Array([0.0, 0.0, 0.0])
  293. ],
  294.  
  295. // Cylinder.
  296. [
  297. new Float32Array([1.0, 1.0, 1.0, 1.0]),
  298. new Float32Array([1.0, 0.0, 0.0])
  299. ],
  300.  
  301. // Cone.
  302. [
  303. new Float32Array([0.0, 0.0, 1.0, 1.0]),
  304. new Float32Array([0.0, 0.0, 1.0])
  305. ],
  306.  
  307. // Pill.
  308. [
  309. new Float32Array([1.0, 1.0, 2.0, 1.0]),
  310. new Float32Array([1.0, 1.0, 0.0])
  311. ],
  312.  
  313. // Sphere.
  314. [
  315. new Float32Array([1.0, 1.0, 1.0, 1.0]),
  316. new Float32Array([1.0, 1.0, 0.0])
  317. ],
  318.  
  319. // Pellet.
  320. [
  321. new Float32Array([1.0, 1.0, 0.25, 1.0]),
  322. new Float32Array([1.0, 0.25, 0.0])
  323. ],
  324.  
  325. // Torus.
  326. [
  327. new Float32Array([1.0, 1.0, 0.25, 0.25]),
  328. new Float32Array([1.0, 0.25, 0.0])
  329. ],
  330.  
  331. // Pipe.
  332. [
  333. new Float32Array([1.0, 1.0, 1.0, 0.25]),
  334. new Float32Array([1.0, 0.1, 0.0])
  335. ],
  336.  
  337. // Corridor.
  338. [
  339. new Float32Array([1.0, 1.0, 1.0, 0.25]),
  340. new Float32Array([0.1, 0.1, 0.0])
  341. ]
  342.  
  343. ];
  344.  
  345. /**
  346. * An enumeration of super primitive presets.
  347. *
  348. * @type {Object}
  349. * @property {Number} CUBE - A cube.
  350. * @property {Number} CYLINDER - A cylinder.
  351. * @property {Number} CONE - A cone.
  352. * @property {Number} PILL - A pill.
  353. * @property {Number} SPHERE - A sphere.
  354. * @property {Number} PELLET - A pellet.
  355. * @property {Number} TORUS - A torus.
  356. * @property {Number} PIPE - A pipe.
  357. * @property {Number} CORRIDOR - A corridor.
  358. */
  359.  
  360. export const SuperPrimitivePreset = {
  361.  
  362. CUBE: 0,
  363. CYLINDER: 1,
  364. CONE: 2,
  365. PILL: 3,
  366. SPHERE: 4,
  367. PELLET: 5,
  368. TORUS: 6,
  369. PIPE: 7,
  370. CORRIDOR: 8
  371.  
  372. };