// noinspection JSUnusedGlobalSymbols
import {ceiling, sqrt, sin, cos, pi} from '@backstrap/math';
import {Shape} from './Shape';
import {Surface} from './Surface';
/**
* Class representing a factory for various simple solid objects.
* @extends Coords
*/
export class Solid extends Shape {
/**
* Generate a block (a cuboid.)
*
* @param {number} [w] - width
* @param {number} [h] - height
* @param {number} [d] - depth
* @returns {Geometry[]}
*/
block(w = 1, h = w, d = h) {
return [
(u, v) => [-w/2, h*(u - 0.5), -d*(v - 0.5)],
(u, v) => [w/2, h*(u - 0.5), d*(v - 0.5)],
(u, v) => [w*(u - 0.5), -h/2, d*(v - 0.5)],
(u, v) => [w*(u - 0.5), h/2, -d*(v - 0.5)],
(u, v) => [w*(u - 0.5), -h*(v - 0.5), -d/2],
(u, v) => [w*(u - 0.5), h*(v - 0.5), d/2],
].map(f => this.surface(f, [0, 1, 1], [0, 1, 1])).flat();
}
/**
* Generate a rod (a solid cylinder, with ends.)
*
* @param {number} [r] - radius
* @param {number} [h] - height
* @returns {Geometry[]}
*/
rod(r = 1, h = 1) {
const surface = new Surface(this);
return surface.cylinder(r, h)
.concat(surface.dupe().translate(0, 0, h*0.5).disc(r))
.concat(surface.dupe().translate(0, 0, -h*0.5).rotate(pi).disc(r));
}
/**
* Generate a solid cone.
*
* @param {number} [r] - radius
* @param {number} [h] - height
* @returns {Geometry[]}
*/
cone(r = 1, h = 1) {
const surface = new Surface(this);
return surface.conic(0, r, h)
.concat(surface.dupe().translate(0, 0, -h*0.5).rotate(pi).disc(r));
}
/**
* Generate a sphere with cubic tesselation.
*
* @param {number} [r] - radius
* @returns {Geometry[]}
*/
sphere(r = 1) {
const dv = ceiling(this.dv/4);
const uvRange = [-0.5, 0.5, dv];
const dist = (u, v) => r/sqrt(u*u + v*v + 0.25);
return [
(u, v) => (p => [ u*p, v*p, p/2])(dist(u, v)),
(u, v) => (p => [ u*p, -v*p, -p/2])(dist(u, v)),
(u, v) => (p => [ p/2, u*p, v*p])(dist(u, v)),
(u, v) => (p => [ u*p, -p/2, v*p])(dist(u, v)),
(u, v) => (p => [-p/2, -u*p, v*p])(dist(u, v)),
(u, v) => (p => [-u*p, p/2, v*p])(dist(u, v)),
].map(f => this.surface(f, uvRange, uvRange)).flat();
}
/**
* Generate a regular torus.
*
* @param {number} [r1] - primary radius
* @param {number} [r2] - secondary radius
* @returns {Geometry[]}
*/
torus(r1 = 2, r2 = 1) {
const uvRange = [0, 2*pi, this.dv];
return this.surface((u, v) => [
(r1 + r2*cos(v))*cos(u),
(r1 + r2*cos(v))*sin(u),
r2*sin(v)
], uvRange, uvRange);
}
/**
* Generate the surface of a general parametric volume.
* The default arguments produce a properly oriented unit cube.
* If drawing "single-sided", note that
* the function f naturally parameterizes the interior
* of the volume, oriented with right-hand-rule,
* so the "upward-facing" normals to the surface g(u, v) = f(u, v, k)
* are outward-facing when k = w[1] (not k = w[0]).
*
* @param {function(number, number, number): number[]} [f] - parameterization of the volume
* @param {number[]} [u] - [start, end, step] for u parameter (default [0, 1, 1])
* @param {number[]} [v] - [start, end, step] for v parameter (default [0, 1, 1])
* @param {number[]} [w] - [start, end, step] for w parameter (default [0, 1, 1])
* @returns {Geometry[]}
*/
volume(f = (...v) => v, u = [0, 1, 1], v = [0, 1, 1], w = [0, 1, 1]) {
const backward = ([start, end, steps]) => [end, start, steps];
return [
this.surface((s, t) => f(s, t, w[0]), u, backward(v)),
this.surface((s, t) => f(s, t, w[1]), u, v),
this.surface((s, t) => f(s, v[0], t), u, w),
this.surface((s, t) => f(s, v[1], t), backward(u), w),
this.surface((s, t) => f(u[0], s, t), v, backward(w)),
this.surface((s, t) => f(u[1], s, t), v, w),
].flat();
}
/**
* Generate the surface of an extruded convex polygon.
* The polygon is drawn in the x-y plane, and extruded along the z-axis.
* The default parameters draw a unit cube.
*
* @param {function(number): number[]} [f] - parameterized cross-section f(t) = [x, y]
* @param {number[]} [t] - [start, end, step] for the cross-section parameterization
* @param {number} [h] - extrusion height
* @returns {Geometry[]}
*/
extrusion(f = t => [cos(t), sin(t)], t = [0, 2*pi], h = 1) {
return [
this.surface((u, v) => v ? [...f(u), h/2] : [0, 0, h/2], t, [0, 1, 1]),
this.surface((u, v) => v ? [...f(u), -h/2] : [0, 0, -h/2], t, [1, 0, 1]),
this.surface((u, v) => [...f(u), v], t, [-h/2, h/2, 1]),
].flat(Infinity);
}
/**
* Generate the surface of an extruded convex polygon.
* The polygon is drawn in the x-y plane, and extruded along the z-axis.
* The default parameters draw a unit cube.
*
* @param {number[][]} [pts] - 2D polygon vertices (must enclose the origin "convex-ly")
* @param {number} [h] - extrusion height
* @returns {Geometry[]}
*/
polygonExtrusion(pts = [[0.5, 0.5], [-0.5, 0.5], [-0.5, -0.5], [0.5, -0.5]], h = 1) {
const vertices = [pts.at(-1), ...pts];
const segments = pts.length - 2;
const f = d => (u, v) => [pts[v ? 0 : 1 + u|0][0], pts[v ? 0 : 1 + u|0][1], d/2];
return [
this.surface(f(h), [segments, 0, segments], [0, 1, 1]),
this.surface(f(-h), [0, segments, segments], [0, 1, 1]),
pts.map(
(pt, n) => this.surface(
(u, v) => [
(u ? pt : vertices[n])[0],
(u ? pt : vertices[n])[1],
h / (v ? 2 : -2),
],
[0, 1, 1], [0, 1, 1]
)
),
].flat(Infinity);
}
}