trid3d 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,7 +16,8 @@ Trid gives you a scene, a camera, shapes, model loading and mouse/keyboard tools
16
16
 
17
17
  - **Scene** — holds a canvas, a camera and your objects, and runs the frame loop for you.
18
18
  - **Groups** — give any object `children` and they move, turn and scale with it (models made of parts, a moon orbiting a planet).
19
- - **Shapes** — cubes, boxes, spheres, cylinders, cones, tori and planes built in, `lathe` for round shapes and `extrude` for flat ones, or any mesh you define as vertices and edges.
19
+ - **Shapes** — cubes, boxes (sharp or rounded), spheres, cylinders, cones, tori and planes built in, `lathe` for round shapes and `extrude` for flat ones, or any mesh you define as vertices and edges.
20
+ - **Modeling** — smooth curves through a few points (`spline`), shapes swept along a 3D path (`sweep`: tails, horns, pipes, rails), surfaces through cross-sections (`loft`: bodies, bottles, hulls, heads), subdivision surfaces with creases (`subdivideMesh`: box modeling), shapes that melt together (`blobMesh`: creatures, clay; or any distance field with `sdfMesh`), rounded or cut edges on any mesh (`bevelMesh`), taper, twist, bend and bulge deformers, and mesh cleanup and checks (weld, clean, validate, fix winding).
20
21
  - **Mesh helpers** — clone, transform, displace, merge and measure meshes; seeded random numbers and smooth noise (`fbm2`, `fbm3`, tileable) for rocks, terrain and procedural textures.
21
22
  - **Filled faces** — give an object a `material` to fill and shade its faces, flat or smooth.
22
23
  - **Lights** — ambient, directional and point lights, with colors and intensities, as many as you need (up to 8 at once besides ambient).
@@ -86,6 +87,7 @@ Or let users load their own: `scene.use(new ObjUploader())` accepts dropped `.ob
86
87
  | `trid/sceneGraph.js` | Groups: `children`, world matrices |
87
88
  | `trid/math/` | Transforms, camera directions, projection, matrices, rotations, random numbers and noise |
88
89
  | `trid/render/` | Renderers (Canvas 2D, WebGL), lights, effects, post effects, shapes, grids, mesh helpers, procedural textures, drawing, frame loop |
90
+ | `trid/modeling/` | Modeling tools: curves, sweep, loft, subdivision, blobs, bevel, deformers, cleanup |
89
91
  | `trid/loaders/` | OBJ, MTL and texture loading |
90
92
  | `trid/export/` | Screenshots, video recording, OBJ and MTL export |
91
93
  | `trid/tools/` | Camera, object, gizmo and overlay tools |
@@ -111,7 +113,7 @@ See [`docs/README.md`](https://github.com/ZachR622/Trid/blob/master/docs/README.
111
113
  npm test
112
114
  ```
113
115
 
114
- Uses Node's built-in test runner (Node 21+), so there's nothing to install. The tests cover shapes, mesh helpers, noise, textures, OBJ / MTL, picking, tools, groups and matrices, lights, post effects, grids, and the WebGL renderer's GPU memory on a fake WebGL context.
116
+ Uses Node's built-in test runner (Node 21+), so there's nothing to install. The tests cover shapes, mesh helpers, curves, sweeps, lofts, subdivision, blobs, bevels, deformers, mesh cleanup, noise, textures, OBJ / MTL, picking, tools, groups and matrices, lights, post effects, grids, and the WebGL renderer's GPU memory on a fake WebGL context.
115
117
 
116
118
  ## Upgrading
117
119
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trid3d",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A small 3D library for the HTML canvas: wireframes, filled and smooth faces, textures and shader effects, drawn with Canvas 2D or WebGL. No dependencies.",
5
5
  "keywords": [
6
6
  "3d",
package/trid/index.d.ts CHANGED
@@ -284,8 +284,10 @@ export interface ShapeOptions {
284
284
  r?: number;
285
285
  /** Sphere latitude bands (default 12) */
286
286
  rings?: number;
287
- /** Points around: sphere (24), cylinder / cone (32), torus (48) */
287
+ /** Points around: sphere (24), cylinder / cone (32), torus (48); rows per rounded edge of a cube / box with `radius` (4) */
288
288
  segments?: number;
289
+ /** Cube / box: round the edges and corners by this much (default 0: sharp) */
290
+ radius?: number;
289
291
  /** Cone top radius: 0 = pointed, > 0 = tapered cylinder (default 0) */
290
292
  rTop?: number;
291
293
  /** Torus: distance from the center to the middle of the tube (default 0.35) */
@@ -1632,3 +1634,320 @@ export function noise3(x: number, y: number, z: number, opts?: NoiseOptions): nu
1632
1634
  export function fbm2(x: number, y: number, opts?: FbmOptions): number;
1633
1635
  /** Fractal 3D noise: octaves of noise3, each twice as fine. About [0, 1), averaging 0.5. */
1634
1636
  export function fbm3(x: number, y: number, z: number, opts?: FbmOptions): number;
1637
+
1638
+ // Modeling: curves
1639
+
1640
+ export interface SplineOptions {
1641
+ /** Points made between two control points (default 8) */
1642
+ segments?: number;
1643
+ /** The last point joins the first (a loop); the result doesn't repeat the first point (default false) */
1644
+ closed?: boolean;
1645
+ /** 0 = uniform, 0.5 = centripetal: no loops or overshoot (default), 1 = chordal */
1646
+ alpha?: number;
1647
+ /** 0 = smooth (default), 1 = straight lines between the points */
1648
+ tension?: number;
1649
+ /** Instead of `segments`: this many points in all, evenly spaced by length */
1650
+ count?: number;
1651
+ /** Instead of `segments`: points about this far apart, evenly spaced */
1652
+ spacing?: number;
1653
+ }
1654
+
1655
+ /**
1656
+ * A smooth curve through `points` (Catmull-Rom: it passes through every
1657
+ * point). Works with 2D, 3D and lathe `{ r, y }` points: every number field
1658
+ * of the first point is a coordinate, and the result has the same fields.
1659
+ */
1660
+ export function spline<P extends Point2 | Vec3 | ProfilePoint>(points: P[], opts?: SplineOptions): P[];
1661
+
1662
+ export interface ResampleOptions {
1663
+ /** Number of points in all */
1664
+ count?: number;
1665
+ /** Distance between points (used when `count` isn't given) */
1666
+ spacing?: number;
1667
+ /** The points form a loop (default false) */
1668
+ closed?: boolean;
1669
+ }
1670
+
1671
+ /** Points along a polyline, evenly spaced by length. The ends stay where they are. */
1672
+ export function resamplePath<P extends Point2 | Vec3 | ProfilePoint>(points: P[], opts?: ResampleOptions): P[];
1673
+
1674
+ // Modeling: sweep
1675
+
1676
+ /** A size along a path: a number, numbers spread evenly from start to end, or a function of t (0 at the start, 1 at the end). */
1677
+ export type SizeAlong = number | number[] | ((t: number) => number);
1678
+
1679
+ export interface SweepOptions {
1680
+ /** The cross-section: x to the path's side, y to its "up" (default: a circle of `radius`) */
1681
+ profile?: Point2[];
1682
+ /** The profile is a closed outline (default true); false = an open line, for ribbons, leaves and roads */
1683
+ profileClosed?: boolean;
1684
+ /** Radius of the default circle (default 0.1). Ignored with a `profile` */
1685
+ radius?: SizeAlong;
1686
+ /** Points around the default circle (default 16) */
1687
+ segments?: number;
1688
+ /** Size of the section along the path (default 1). 0 at an end closes it into a point */
1689
+ scale?: SizeAlong;
1690
+ /** Turn of the section from start to end, in radians (default 0). Whole turns on a closed path */
1691
+ twist?: number;
1692
+ /** The path is a loop: its end joins its start (default false) */
1693
+ closed?: boolean;
1694
+ /** Close the ends with flat caps (default true; open paths with closed profiles) */
1695
+ caps?: boolean;
1696
+ /** Keep the section's y pointed this way (roads, rails, frames). Default: carried along the path without spinning */
1697
+ up?: Vec3;
1698
+ /** Stretch the section at sharp corners so the tube keeps its thickness (default true) */
1699
+ miter?: boolean;
1700
+ }
1701
+
1702
+ /**
1703
+ * Moves a cross-section along a 3D path: tails, horns, tentacles, handles,
1704
+ * pipes, cables, rails. Without `profile`, a round tube of `radius`.
1705
+ * Clockwise faces; uvs: u around the section, v along the path.
1706
+ */
1707
+ export function sweep(path: Vec3[], opts?: SweepOptions): Mesh;
1708
+
1709
+ // Modeling: loft
1710
+
1711
+ /** A 2D section, placed along the loft's axis or path. */
1712
+ export interface LoftOutline {
1713
+ /** Closed outline: x to the side, y up (either direction; one point = a tip) */
1714
+ outline: Point2[];
1715
+ /** Where it is: the position on the axis, or 0 (start) to 1 (end) along a path (default: evenly spread) */
1716
+ at?: number;
1717
+ /** Size, the same both ways or per direction (default 1); 0 = a tip */
1718
+ scale?: number | { x?: number; y?: number };
1719
+ /** Turn in its own plane, in radians (default 0) */
1720
+ rotate?: number;
1721
+ /** Move in its own plane (default none) */
1722
+ offset?: Partial<Point2>;
1723
+ }
1724
+
1725
+ /** A section as a ring of 3D points, used where it is. */
1726
+ export interface LoftRing {
1727
+ points: Vec3[];
1728
+ }
1729
+
1730
+ export interface LoftOptions {
1731
+ /** Straight axis the outlines are placed along (default "z"). x/y of an outline: "z" → X/Y, "y" → X/Z, "x" → Z/Y */
1732
+ axis?: "x" | "y" | "z";
1733
+ /** A 3D path to place the outlines along instead of the axis */
1734
+ path?: Vec3[];
1735
+ /** With `path`: keep the outlines' y pointed this way (default: carried along without spinning) */
1736
+ up?: Vec3;
1737
+ /** The surface loops back to its first section (a closed path, or 3D rings in a loop; default false) */
1738
+ closed?: boolean;
1739
+ /** Close the ends with flat caps (default true) */
1740
+ caps?: boolean;
1741
+ /** Rows between two sections, on average (default 6) */
1742
+ segments?: number;
1743
+ /** Points around each section (default: kept when every section has the same count, else the most any has, at least 24) */
1744
+ points?: number;
1745
+ /** Smooth curves between sections, never past them (default true); false = straight */
1746
+ smooth?: boolean;
1747
+ /** Line sections up so the surface doesn't twist (default true) */
1748
+ align?: boolean;
1749
+ }
1750
+
1751
+ /**
1752
+ * A surface through cross-sections: bodies, bottles, hulls, car bodies,
1753
+ * heads. Every section is an `outline` (2D, placed along an axis or a path)
1754
+ * or every section is `points` (a 3D ring). Clockwise faces; uvs: u around,
1755
+ * v along. Throws when the two kinds are mixed.
1756
+ */
1757
+ export function loft(sections: LoftOutline[] | LoftRing[], opts?: LoftOptions): Mesh;
1758
+
1759
+ // Modeling: deformers
1760
+
1761
+ export interface AxisRange {
1762
+ /** Axis the effect goes along (default "y") */
1763
+ axis?: "x" | "y" | "z";
1764
+ /** Start of the part that changes: 0 = the mesh's low end on the axis, 1 = its high end (default 0) */
1765
+ from?: number;
1766
+ /** End of the part that changes, same measure (default 1). Past it, the full effect carries on */
1767
+ to?: number;
1768
+ /** A point the axis line goes through (default: the middle of the mesh's box) */
1769
+ center?: Partial<Vec3>;
1770
+ }
1771
+
1772
+ export interface TaperOptions extends AxisRange {
1773
+ /** Size at the end of the range (default 0.5), or a function of how far into the range (0 to 1) giving the size */
1774
+ amount?: number | ((w: number) => number);
1775
+ }
1776
+
1777
+ export interface TwistOptions extends AxisRange {
1778
+ /** Turn at the end of the range, in radians (default π/2), or a function of how far into the range (0 to 1) */
1779
+ angle?: number | ((w: number) => number);
1780
+ }
1781
+
1782
+ export interface BendOptions extends AxisRange {
1783
+ /** Angle of the arc, in radians (default π/2); negative curves the other way */
1784
+ angle?: number;
1785
+ /** Axis it curves toward (default: "z" for a "y" axis, "z" for "x", "y" for "z") */
1786
+ toward?: "x" | "y" | "z";
1787
+ }
1788
+
1789
+ export interface BulgeOptions {
1790
+ /** Middle of the bulge */
1791
+ center: Vec3;
1792
+ /** Distance where it fades to nothing */
1793
+ radius: number;
1794
+ /** How far the middle moves (default 0.1); negative = a dent */
1795
+ amount?: number;
1796
+ /** "normal": along the surface, works on flat sides (default); "radial": away from `center` (put it inside the part) */
1797
+ mode?: "normal" | "radial";
1798
+ }
1799
+
1800
+ /** Narrows or widens a mesh along an axis, in place. Returns the same mesh. */
1801
+ export function taperMesh<M extends Mesh>(mesh: M, opts?: TaperOptions): M;
1802
+ /** Turns a mesh around an axis, more and more along it, in place (same direction as rotateX / Y / Z). Returns the same mesh. */
1803
+ export function twistMesh<M extends Mesh>(mesh: M, opts?: TwistOptions): M;
1804
+ /** Bends a mesh into an arc along an axis, in place; past the range it goes on straight. Returns the same mesh. */
1805
+ export function bendMesh<M extends Mesh>(mesh: M, opts?: BendOptions): M;
1806
+ /** Pushes the surface out (or in) around a point, fading at `radius`, in place. Returns the same mesh. */
1807
+ export function bulgeMesh<M extends Mesh>(mesh: M, opts: BulgeOptions): M;
1808
+
1809
+ // Modeling: cleanup and checks
1810
+
1811
+ /**
1812
+ * Merges vertices closer than `epsilon` (default: a ten-millionth of the
1813
+ * mesh's size), in place. Texture coordinates and normals (per corner) are
1814
+ * kept; corners that meet are removed. Returns the same mesh.
1815
+ */
1816
+ export function weldVertices<M extends Mesh>(mesh: M, epsilon?: number): M;
1817
+
1818
+ export interface CleanOptions {
1819
+ /** Merge vertices at the same place first: true, or the distance (default false) */
1820
+ weld?: boolean | number;
1821
+ }
1822
+
1823
+ /**
1824
+ * Removes what's broken, in place: faces to missing or bad vertices, repeated
1825
+ * corners, faces with fewer than 3 corners, crossed or of no area, repeated
1826
+ * faces, bad corner lists (set to null), bad edges, and unused vertices,
1827
+ * texture coordinates and normals. Returns the same mesh.
1828
+ */
1829
+ export function cleanMesh<M extends Mesh>(mesh: M, opts?: CleanOptions): M;
1830
+
1831
+ export interface MeshProblem {
1832
+ /** What's wrong, e.g. "bad-index", "no-area", "turned-face", "inside-out", "open-edge" */
1833
+ kind: string;
1834
+ /** "error": breaks drawing or export; "warning": draws wrong; "info": worth knowing */
1835
+ level: "error" | "warning" | "info";
1836
+ /** A short sentence, with the first few faces / vertices / edges concerned */
1837
+ message: string;
1838
+ faces?: number[];
1839
+ vertices?: number[];
1840
+ edges?: number[];
1841
+ }
1842
+
1843
+ /** Lists what's wrong with a mesh, without changing it (one entry per kind of problem; empty when fine). */
1844
+ export function validateMesh(mesh: Mesh): MeshProblem[];
1845
+
1846
+ /**
1847
+ * Turns faces in place so neighbors agree and each piece points outward
1848
+ * (clockwise from outside). Works without welding. Returns the same mesh.
1849
+ */
1850
+ export function fixWinding<M extends Mesh>(mesh: M): M;
1851
+
1852
+ // Modeling: subdivision
1853
+
1854
+ export interface SubdivideOptions {
1855
+ /** Times to subdivide (default 1, at most 6); each level makes about 4 times the faces */
1856
+ levels?: number;
1857
+ /** false: only split the faces, keep the shape (default true) */
1858
+ smooth?: boolean;
1859
+ /** Edges where faces meet sharper than this (degrees) stay hard (default: none) */
1860
+ creaseAngle?: number;
1861
+ /** Edges that stay hard: pairs or polylines of vertex indices, e.g. [[0, 1], [4, 5, 6, 7, 4]] */
1862
+ creases?: number[][];
1863
+ /** "smooth": texture coordinates smoothed with the surface, seams kept (default); "linear": only split */
1864
+ uvs?: "smooth" | "linear";
1865
+ }
1866
+
1867
+ /**
1868
+ * Smooths a mesh by subdividing it (Catmull-Clark): a rough cage becomes a
1869
+ * smooth surface. Returns a new mesh of quads (the original isn't changed),
1870
+ * welded, with texture coordinates and materials; own normals are dropped.
1871
+ */
1872
+ export function subdivideMesh(mesh: Mesh, opts?: SubdivideOptions): Mesh;
1873
+
1874
+ // Modeling: blobs (distance fields)
1875
+
1876
+ /** How a blob shape joins what's before it: melts in (default), carves it, or keeps only the overlap. */
1877
+ export type BlobOp = "add" | "subtract" | "intersect";
1878
+
1879
+ interface BlobBase {
1880
+ /** How it joins what's before it (default "add") */
1881
+ op?: BlobOp;
1882
+ /** Same as op: "subtract" */
1883
+ subtract?: boolean;
1884
+ /** How far it melts into what's before it, as a distance (default: the blobMesh / blobField `blend`) */
1885
+ blend?: number;
1886
+ }
1887
+
1888
+ export type BlobShape = BlobBase & (
1889
+ | { kind: "sphere"; center: Vec3; radius: number }
1890
+ /** Turned by `rot` (radians, X then Y then Z) */
1891
+ | { kind: "ellipsoid"; center: Vec3; radii: Vec3; rot?: Vec3 }
1892
+ /** A capsule between two points; `ra` / `rb` instead of `radius` make a rounded cone */
1893
+ | { kind: "capsule"; a: Vec3; b: Vec3; radius?: number; ra?: number; rb?: number }
1894
+ /** A capped cylinder between two points, edges rounded by `round` */
1895
+ | { kind: "cylinder"; a: Vec3; b: Vec3; radius: number; round?: number }
1896
+ /** A box of `size`, edges rounded by `round`, turned by `rot` */
1897
+ | { kind: "box"; center: Vec3; size: Vec3; round?: number; rot?: Vec3 }
1898
+ /** A ring lying flat in XZ (like makeShape("torus")), turned by `rot` */
1899
+ | { kind: "torus"; center: Vec3; R: number; tube: number; rot?: Vec3 }
1900
+ );
1901
+
1902
+ export interface SdfMeshOptions {
1903
+ /** Grid cells along the longest side (default 64, 4 to 256); more is finer and slower */
1904
+ resolution?: number;
1905
+ /** "nets": surface nets, even quads (default); "cubes": marching cubes, triangles. Both are watertight */
1906
+ method?: "nets" | "cubes";
1907
+ /** Pull vertices exactly onto the surface (default true) */
1908
+ project?: boolean;
1909
+ /** Normals from the field, for smooth shading (default true) */
1910
+ normals?: boolean;
1911
+ /** Texture mapping for the uvs, as for computeUVs (default "box"); false: none */
1912
+ uvs?: MappingType | Mapping | false;
1913
+ /** Skip grid blocks far from the surface, trusting the field to be about the distance (default true) */
1914
+ sparse?: boolean;
1915
+ }
1916
+
1917
+ export interface BlobMeshOptions extends Omit<SdfMeshOptions, "sparse"> {
1918
+ /** How far shapes melt into each other, as a distance (default: a tenth of the model's size). A shape's own `blend` wins */
1919
+ blend?: number;
1920
+ }
1921
+
1922
+ /** One smooth mesh of shapes that melt together (added, subtracted or intersected, in order). */
1923
+ export function blobMesh(shapes: BlobShape[], opts?: BlobMeshOptions): Mesh;
1924
+
1925
+ /** The distance field of blob shapes (negative inside), to mix with your own math and mesh with sdfMesh. */
1926
+ export function blobField(shapes: BlobShape[], opts?: { blend?: number }): (p: Vec3) => number;
1927
+
1928
+ /** The surface where `field(p)` is 0 (negative inside), within `bounds`, as a mesh. */
1929
+ export function sdfMesh(field: (p: Vec3) => number, bounds: { min: Vec3; max: Vec3 }, opts?: SdfMeshOptions): Mesh;
1930
+
1931
+ // Modeling: bevel
1932
+
1933
+ export interface BevelOptions {
1934
+ /** Size of the rounding: the radius of the ball rolled along the edges (default: a twentieth of the mesh's size). Shrinks where bevels would overlap */
1935
+ radius?: number;
1936
+ /** Rows across each rounded edge (default 3); 1 = a flat cut (chamfer) */
1937
+ segments?: number;
1938
+ /** Edges where faces meet sharper than this (degrees) are beveled (default 30) */
1939
+ angle?: number;
1940
+ /** "edges": round the edges, corners become pieces of a sphere (default); "vertices": cut only the corners */
1941
+ mode?: "edges" | "vertices";
1942
+ /** Instead of `angle`: the edges to bevel, as pairs or polylines of vertex indices */
1943
+ edges?: number[][];
1944
+ /** With mode "vertices", instead of `angle`: the corners to cut, as vertex indices */
1945
+ vertices?: number[];
1946
+ }
1947
+
1948
+ /**
1949
+ * Rounds (or cuts flat) the sharp edges of a mesh, like a bevel modifier.
1950
+ * Returns a new mesh, welded, with texture coordinates and materials kept.
1951
+ * Edges along an open border stay sharp.
1952
+ */
1953
+ export function bevelMesh(mesh: Mesh, opts?: BevelOptions): Mesh;
package/trid/index.js CHANGED
@@ -81,3 +81,13 @@ export {
81
81
 
82
82
  // Random numbers and noise
83
83
  export { rng, noise2, noise3, fbm2, fbm3 } from "./math/noise.js";
84
+
85
+ // Modeling
86
+ export { spline, resamplePath } from "./modeling/curves.js";
87
+ export { sweep } from "./modeling/sweep.js";
88
+ export { loft } from "./modeling/loft.js";
89
+ export { taperMesh, twistMesh, bendMesh, bulgeMesh } from "./modeling/deform.js";
90
+ export { weldVertices, cleanMesh, validateMesh, fixWinding } from "./modeling/cleanup.js";
91
+ export { subdivideMesh } from "./modeling/subdivide.js";
92
+ export { blobMesh, blobField, sdfMesh } from "./modeling/blobs.js";
93
+ export { bevelMesh } from "./modeling/bevel.js";