src/volume/sdf/SuperPrimitive.js
- import { Box3, Vector2, Vector3, Vector4 } from "three";
- import { SignedDistanceFunction } from "./SignedDistanceFunction";
- import { SDFType } from "./SDFType";
-
- /**
- * The super primitive.
- *
- * A function that is able to represent a wide range of conic/rectangular-radial
- * primitives of genus 0 and 1: (round) box, sphere, cylinder, capped cone,
- * torus, capsule, pellet, pipe, etc.
- *
- * Reference:
- * https://www.shadertoy.com/view/MsVGWG
- */
-
- export class SuperPrimitive extends SignedDistanceFunction {
-
- /**
- * Constructs a new super primitive.
- *
- * See {@link SuperPrimitivePreset} for a list of default configurations.
- *
- * @param {Object} parameters - The parameters.
- * @param {Array} parameters.s - The size and genus weight [x, y, z, w].
- * @param {Array} parameters.r - The corner radii [x, y, z].
- * @param {Number} [material] - A material index.
- * @example const cube = SuperPrimitive.create(SuperPrimitivePreset.CUBE);
- */
-
- constructor(parameters = {}, material) {
-
- super(SDFType.SUPER_PRIMITIVE, material);
-
- /**
- * The base size. The W-component affects the genus of the primitive.
- *
- * @type {Vector4}
- * @private
- */
-
- this.s0 = new Vector4(...parameters.s);
-
- /**
- * The base corner radii.
- *
- * @type {Vector3}
- * @private
- */
-
- this.r0 = new Vector3(...parameters.r);
-
- /**
- * The size, adjusted for further calculations.
- *
- * @type {Vector4}
- * @private
- */
-
- this.s = new Vector4();
-
- /**
- * The corner radii, adjusted for further calculations.
- *
- * @type {Vector3}
- * @private
- */
-
- this.r = new Vector3();
-
- /**
- * Precomputed corner rounding constants.
- *
- * @type {Vector2}
- * @private
- */
-
- this.ba = new Vector2();
-
- /**
- * The bottom radius offset.
- *
- * @type {Number}
- * @private
- */
-
- this.offset = 0;
-
- // Calculate constants ahead of time.
- this.precompute();
-
- }
-
- /**
- * Sets the size and genus weight.
- *
- * @param {Number} x - X.
- * @param {Number} y - Y.
- * @param {Number} z - Z.
- * @param {Number} w - W.
- * @return {SuperPrimitive} This instance.
- */
-
- setSize(x, y, z, w) {
-
- this.s0.set(x, y, z, w);
-
- return this.precompute();
-
- }
-
- /**
- * Sets the corner radii.
- *
- * @param {Number} x - X.
- * @param {Number} y - Y.
- * @param {Number} z - Z.
- * @return {SuperPrimitive} This instance.
- */
-
- setRadii(x, y, z) {
-
- this.r0.set(x, y, z);
-
- return this.precompute();
-
- }
-
- /**
- * Precomputes corner rounding factors.
- *
- * @private
- * @return {SuperPrimitive} This instance.
- */
-
- precompute() {
-
- const s = this.s.copy(this.s0);
- const r = this.r.copy(this.r0);
- const ba = this.ba;
-
- s.x -= r.x;
- s.y -= r.x;
-
- r.x -= s.w;
- s.w -= r.y;
-
- s.z -= r.y;
-
- this.offset = -2.0 * s.z;
-
- ba.set(r.z, this.offset);
- const divisor = ba.dot(ba);
-
- if(divisor === 0.0) {
-
- // Y must not be 0 to prevent bad values for Z = 0 in the last term (*).
- ba.set(0.0, -1.0);
-
- } else {
-
- ba.divideScalar(divisor);
-
- }
-
- return this;
-
- }
-
- /**
- * Calculates the bounding box of this SDF.
- *
- * @return {Box3} The bounding box.
- */
-
- computeBoundingBox() {
-
- const s = this.s0;
- const boundingBox = new Box3();
-
- boundingBox.min.x = Math.min(-s.x, -1.0);
- boundingBox.min.y = Math.min(-s.y, -1.0);
- boundingBox.min.z = Math.min(-s.z, -1.0);
-
- boundingBox.max.x = Math.max(s.x, 1.0);
- boundingBox.max.y = Math.max(s.y, 1.0);
- boundingBox.max.z = Math.max(s.z, 1.0);
-
- boundingBox.applyMatrix4(this.getTransformation());
-
- return boundingBox;
-
- }
-
- /**
- * Samples the volume's density at the given point in space.
- *
- * @param {Vector3} position - A position.
- * @return {Number} The euclidean distance to the surface.
- */
-
- sample(position) {
-
- position.applyMatrix4(this.inverseTransformation);
-
- const s = this.s;
- const r = this.r;
- const ba = this.ba;
-
- const dx = Math.abs(position.x) - s.x;
- const dy = Math.abs(position.y) - s.y;
- const dz = Math.abs(position.z) - s.z;
-
- const mx0 = Math.max(dx, 0.0);
- const my0 = Math.max(dy, 0.0);
- const l0 = Math.sqrt(mx0 * mx0 + my0 * my0);
-
- const p = position.z - s.z;
- const q = Math.abs(l0 + Math.min(0.0, Math.max(dx, dy)) - r.x) - s.w;
-
- const c = Math.min(Math.max(q * ba.x + p * ba.y, 0.0), 1.0);
- const diagX = q - r.z * c;
- const diagY = p - this.offset * c;
-
- const hx0 = Math.max(q - r.z, 0.0);
- const hy0 = position.z + s.z;
- const hx1 = Math.max(q, 0.0);
- // hy1 = p;
-
- const diagSq = diagX * diagX + diagY * diagY;
- const h0Sq = hx0 * hx0 + hy0 * hy0;
- const h1Sq = hx1 * hx1 + p * p;
- const paBa = q * -ba.y + p * ba.x;
-
- const l1 = Math.sqrt(Math.min(diagSq, Math.min(h0Sq, h1Sq)));
-
- // (*) paBa must not be 0: if dz is also 0, the result will be wrong.
- return l1 * Math.sign(Math.max(paBa, dz)) - r.y;
-
- }
-
- /**
- * Serialises this SDF.
- *
- * @param {Boolean} [deflate=false] - Whether the data should be compressed if possible.
- * @return {Object} The serialised data.
- */
-
- serialize(deflate = false) {
-
- const result = super.serialize();
-
- result.parameters = {
- s: this.s0.toArray(),
- r: this.r0.toArray()
- };
-
- return result;
-
- }
-
- /**
- * Creates a new primitive using the specified preset.
- *
- * @param {SuperPrimitivePreset} preset - The super primitive preset.
- */
-
- static create(preset) {
-
- const parameters = superPrimitivePresets[preset];
-
- return new SuperPrimitive({
- s: parameters[0],
- r: parameters[1]
- });
-
- }
-
- }
-
- /**
- * A list of parameter presets.
- *
- * @type {Array<Float32Array[]>}
- * @private
- */
-
- const superPrimitivePresets = [
-
- // Cube.
- [
- new Float32Array([1.0, 1.0, 1.0, 1.0]),
- new Float32Array([0.0, 0.0, 0.0])
- ],
-
- // Cylinder.
- [
- new Float32Array([1.0, 1.0, 1.0, 1.0]),
- new Float32Array([1.0, 0.0, 0.0])
- ],
-
- // Cone.
- [
- new Float32Array([0.0, 0.0, 1.0, 1.0]),
- new Float32Array([0.0, 0.0, 1.0])
- ],
-
- // Pill.
- [
- new Float32Array([1.0, 1.0, 2.0, 1.0]),
- new Float32Array([1.0, 1.0, 0.0])
- ],
-
- // Sphere.
- [
- new Float32Array([1.0, 1.0, 1.0, 1.0]),
- new Float32Array([1.0, 1.0, 0.0])
- ],
-
- // Pellet.
- [
- new Float32Array([1.0, 1.0, 0.25, 1.0]),
- new Float32Array([1.0, 0.25, 0.0])
- ],
-
- // Torus.
- [
- new Float32Array([1.0, 1.0, 0.25, 0.25]),
- new Float32Array([1.0, 0.25, 0.0])
- ],
-
- // Pipe.
- [
- new Float32Array([1.0, 1.0, 1.0, 0.25]),
- new Float32Array([1.0, 0.1, 0.0])
- ],
-
- // Corridor.
- [
- new Float32Array([1.0, 1.0, 1.0, 0.25]),
- new Float32Array([0.1, 0.1, 0.0])
- ]
-
- ];
-
- /**
- * An enumeration of super primitive presets.
- *
- * @type {Object}
- * @property {Number} CUBE - A cube.
- * @property {Number} CYLINDER - A cylinder.
- * @property {Number} CONE - A cone.
- * @property {Number} PILL - A pill.
- * @property {Number} SPHERE - A sphere.
- * @property {Number} PELLET - A pellet.
- * @property {Number} TORUS - A torus.
- * @property {Number} PIPE - A pipe.
- * @property {Number} CORRIDOR - A corridor.
- */
-
- export const SuperPrimitivePreset = {
-
- CUBE: 0,
- CYLINDER: 1,
- CONE: 2,
- PILL: 3,
- SPHERE: 4,
- PELLET: 5,
- TORUS: 6,
- PIPE: 7,
- CORRIDOR: 8
-
- };