3d-spinner 0.9.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.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +177 -0
  3. package/dist/animation.d.ts +28 -0
  4. package/dist/animation.js +1 -0
  5. package/dist/animations/object-motion.d.ts +96 -0
  6. package/dist/animations/object-motion.js +339 -0
  7. package/dist/animations/spin.d.ts +44 -0
  8. package/dist/animations/spin.js +108 -0
  9. package/dist/engines/little-3d-engine/core/camera.d.ts +26 -0
  10. package/dist/engines/little-3d-engine/core/camera.js +32 -0
  11. package/dist/engines/little-3d-engine/core/geometry.d.ts +27 -0
  12. package/dist/engines/little-3d-engine/core/geometry.js +87 -0
  13. package/dist/engines/little-3d-engine/core/light.d.ts +31 -0
  14. package/dist/engines/little-3d-engine/core/light.js +35 -0
  15. package/dist/engines/little-3d-engine/core/math.d.ts +50 -0
  16. package/dist/engines/little-3d-engine/core/math.js +109 -0
  17. package/dist/engines/little-3d-engine/core/mesh.d.ts +22 -0
  18. package/dist/engines/little-3d-engine/core/mesh.js +8 -0
  19. package/dist/engines/little-3d-engine/little-3d-engine.d.ts +75 -0
  20. package/dist/engines/little-3d-engine/little-3d-engine.js +167 -0
  21. package/dist/engines/little-3d-engine/loaders/obj.d.ts +24 -0
  22. package/dist/engines/little-3d-engine/loaders/obj.js +48 -0
  23. package/dist/engines/little-3d-engine/renderer.d.ts +43 -0
  24. package/dist/engines/little-3d-engine/renderer.js +16 -0
  25. package/dist/engines/little-3d-engine/renderers/canvas2d.d.ts +12 -0
  26. package/dist/engines/little-3d-engine/renderers/canvas2d.js +73 -0
  27. package/dist/engines/little-3d-engine/renderers/webgl.d.ts +15 -0
  28. package/dist/engines/little-3d-engine/renderers/webgl.js +146 -0
  29. package/dist/engines/little-3d-engine/renderers/webgpu.d.ts +24 -0
  30. package/dist/engines/little-3d-engine/renderers/webgpu.js +222 -0
  31. package/dist/engines/little-3d-engine/shapes/cube-sphere.d.ts +11 -0
  32. package/dist/engines/little-3d-engine/shapes/cube-sphere.js +50 -0
  33. package/dist/engines/little-3d-engine/shapes/cube.d.ts +9 -0
  34. package/dist/engines/little-3d-engine/shapes/cube.js +30 -0
  35. package/dist/engines/little-3d-engine/shapes/icosphere.d.ts +11 -0
  36. package/dist/engines/little-3d-engine/shapes/icosphere.js +51 -0
  37. package/dist/engines/little-3d-engine/shapes/octa-sphere.d.ts +10 -0
  38. package/dist/engines/little-3d-engine/shapes/octa-sphere.js +31 -0
  39. package/dist/engines/little-3d-engine/shapes/octahedron.d.ts +8 -0
  40. package/dist/engines/little-3d-engine/shapes/octahedron.js +38 -0
  41. package/dist/engines/little-3d-engine/shapes/pyramid.d.ts +9 -0
  42. package/dist/engines/little-3d-engine/shapes/pyramid.js +26 -0
  43. package/dist/engines/little-3d-engine/shapes/tetrahedron.d.ts +8 -0
  44. package/dist/engines/little-3d-engine/shapes/tetrahedron.js +23 -0
  45. package/dist/engines/little-3d-engine/shapes/uv-sphere.d.ts +10 -0
  46. package/dist/engines/little-3d-engine/shapes/uv-sphere.js +50 -0
  47. package/dist/engines/little-tween-engine/core/tweens.d.ts +39 -0
  48. package/dist/engines/little-tween-engine/core/tweens.js +231 -0
  49. package/dist/engines/little-tween-engine/little-tween-engine.d.ts +21 -0
  50. package/dist/engines/little-tween-engine/little-tween-engine.js +16 -0
  51. package/dist/index.d.ts +37 -0
  52. package/dist/index.js +110 -0
  53. package/dist/motion/circle.d.ts +17 -0
  54. package/dist/motion/circle.js +20 -0
  55. package/dist/motion/controller.d.ts +13 -0
  56. package/dist/motion/controller.js +1 -0
  57. package/dist/motion/figure-eight.d.ts +13 -0
  58. package/dist/motion/figure-eight.js +23 -0
  59. package/dist/motion/motion.d.ts +5 -0
  60. package/dist/motion/motion.js +4 -0
  61. package/dist/motion/square.d.ts +17 -0
  62. package/dist/motion/square.js +39 -0
  63. package/dist/motion/transitions.d.ts +44 -0
  64. package/dist/motion/transitions.js +60 -0
  65. package/dist/motion/wander.d.ts +22 -0
  66. package/dist/motion/wander.js +50 -0
  67. package/dist/progress-animation.d.ts +55 -0
  68. package/dist/progress-animation.js +128 -0
  69. package/package.json +74 -0
@@ -0,0 +1,222 @@
1
+ import { expandToTriangles, parseColor } from "../core/geometry.js";
2
+ import { multiply } from "../core/math.js";
3
+ const WGSL = `
4
+ struct Uniforms {
5
+ viewProj: mat4x4<f32>,
6
+ model: mat4x4<f32>,
7
+ toLight: vec4<f32>,
8
+ params: vec4<f32>,
9
+ };
10
+ @group(0) @binding(0) var<uniform> u: Uniforms;
11
+
12
+ struct VSOut {
13
+ @builtin(position) position: vec4<f32>,
14
+ @location(0) normal: vec3<f32>,
15
+ @location(1) color: vec3<f32>,
16
+ };
17
+
18
+ @vertex
19
+ fn vs(@location(0) pos: vec3<f32>, @location(1) normal: vec3<f32>, @location(2) color: vec3<f32>) -> VSOut {
20
+ var out: VSOut;
21
+ let m = mat3x3<f32>(u.model[0].xyz, u.model[1].xyz, u.model[2].xyz);
22
+ out.normal = m * normal;
23
+ out.color = color;
24
+ out.position = u.viewProj * u.model * vec4<f32>(pos, 1.0);
25
+ return out;
26
+ }
27
+
28
+ @fragment
29
+ fn fs(in: VSOut) -> @location(0) vec4<f32> {
30
+ let lambert = max(dot(normalize(in.normal), normalize(u.toLight.xyz)), 0.0);
31
+ let brightness = clamp(u.params.y + u.params.x * lambert, 0.0, 1.0);
32
+ return vec4<f32>(in.color * brightness, 1.0);
33
+ }
34
+ `;
35
+ // Maps OpenGL clip space (z in -1..1) to WebGPU clip space (z in 0..1).
36
+ const CLIP_Z_FIX = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 1];
37
+ const UNIFORM_STRIDE = 256;
38
+ /** Hardware renderer using WebGPU: GPU transforms with a real depth buffer. */
39
+ export class WebGPURenderer {
40
+ constructor(options = {}) {
41
+ this.uniformCapacity = 0;
42
+ this.depthSize = "";
43
+ this.destroyed = false;
44
+ this.cache = new Map();
45
+ if (options.background) {
46
+ const [r, g, b] = parseColor(options.background);
47
+ this.clearValue = { r: r / 255, g: g / 255, b: b / 255, a: 1 };
48
+ this.alphaMode = "opaque";
49
+ }
50
+ else {
51
+ this.clearValue = { r: 0, g: 0, b: 0, a: 0 };
52
+ this.alphaMode = "premultiplied";
53
+ }
54
+ }
55
+ async init(canvas) {
56
+ const gpu = navigator.gpu;
57
+ if (!gpu)
58
+ throw new Error("3d-spinner: WebGPU is not supported in this browser.");
59
+ const adapter = await gpu.requestAdapter();
60
+ if (!adapter)
61
+ throw new Error("3d-spinner: no WebGPU adapter is available.");
62
+ const device = await adapter.requestDevice();
63
+ if (this.destroyed) {
64
+ device.destroy?.();
65
+ return;
66
+ }
67
+ const context = canvas.getContext("webgpu");
68
+ if (!context)
69
+ throw new Error("3d-spinner: could not get a WebGPU canvas context.");
70
+ const format = gpu.getPreferredCanvasFormat();
71
+ context.configure({ device, format, alphaMode: this.alphaMode });
72
+ const module = device.createShaderModule({ code: WGSL });
73
+ const stage = globalThis.GPUShaderStage;
74
+ const layout = device.createBindGroupLayout({
75
+ entries: [
76
+ {
77
+ binding: 0,
78
+ visibility: stage.VERTEX | stage.FRAGMENT,
79
+ buffer: { type: "uniform", hasDynamicOffset: true, minBindingSize: 160 },
80
+ },
81
+ ],
82
+ });
83
+ const vertexBuffer = (location) => ({
84
+ arrayStride: 12,
85
+ attributes: [{ shaderLocation: location, offset: 0, format: "float32x3" }],
86
+ });
87
+ this.pipeline = device.createRenderPipeline({
88
+ layout: device.createPipelineLayout({ bindGroupLayouts: [layout] }),
89
+ vertex: {
90
+ module,
91
+ entryPoint: "vs",
92
+ buffers: [vertexBuffer(0), vertexBuffer(1), vertexBuffer(2)],
93
+ },
94
+ fragment: { module, entryPoint: "fs", targets: [{ format }] },
95
+ primitive: { topology: "triangle-list", cullMode: "back", frontFace: "ccw" },
96
+ depthStencil: { format: "depth24plus", depthWriteEnabled: true, depthCompare: "less" },
97
+ });
98
+ this.canvas = canvas;
99
+ this.device = device;
100
+ this.context = context;
101
+ }
102
+ resize() {
103
+ this.ensureDepth();
104
+ }
105
+ ensureDepth() {
106
+ const canvas = this.canvas;
107
+ if (!this.device || !canvas)
108
+ return;
109
+ const width = Math.max(1, canvas.width);
110
+ const height = Math.max(1, canvas.height);
111
+ const key = `${width}x${height}`;
112
+ if (key === this.depthSize && this.depthTexture)
113
+ return;
114
+ this.depthTexture?.destroy?.();
115
+ this.depthTexture = this.device.createTexture({
116
+ size: { width, height },
117
+ format: "depth24plus",
118
+ usage: globalThis.GPUTextureUsage.RENDER_ATTACHMENT,
119
+ });
120
+ this.depthSize = key;
121
+ }
122
+ buffers(mesh) {
123
+ const cached = this.cache.get(mesh);
124
+ if (cached)
125
+ return cached;
126
+ const data = expandToTriangles(mesh);
127
+ const usage = globalThis.GPUBufferUsage.VERTEX | globalThis.GPUBufferUsage.COPY_DST;
128
+ const upload = (array) => {
129
+ const buffer = this.device.createBuffer({ size: array.byteLength, usage });
130
+ this.device.queue.writeBuffer(buffer, 0, array);
131
+ return buffer;
132
+ };
133
+ const result = {
134
+ position: upload(data.positions),
135
+ normal: upload(data.normals),
136
+ color: upload(data.colors),
137
+ count: data.count,
138
+ };
139
+ this.cache.set(mesh, result);
140
+ return result;
141
+ }
142
+ ensureUniformCapacity(items) {
143
+ if (items <= this.uniformCapacity && this.uniformBuffer)
144
+ return;
145
+ this.uniformBuffer?.destroy?.();
146
+ this.uniformBuffer = this.device.createBuffer({
147
+ size: Math.max(1, items) * UNIFORM_STRIDE,
148
+ usage: globalThis.GPUBufferUsage.UNIFORM | globalThis.GPUBufferUsage.COPY_DST,
149
+ });
150
+ this.uniformCapacity = items;
151
+ }
152
+ render(frame) {
153
+ if (this.destroyed || !this.device || !this.context || !this.pipeline)
154
+ return;
155
+ if (frame.width === 0 || frame.height === 0 || frame.items.length === 0)
156
+ return;
157
+ this.ensureDepth();
158
+ this.ensureUniformCapacity(frame.items.length);
159
+ const viewProj = multiply(CLIP_Z_FIX, frame.viewProjection);
160
+ const layout = this.pipeline.getBindGroupLayout(0);
161
+ const bindGroup = this.device.createBindGroup({
162
+ layout,
163
+ entries: [
164
+ { binding: 0, resource: { buffer: this.uniformBuffer, offset: 0, size: 160 } },
165
+ ],
166
+ });
167
+ frame.items.forEach((item, i) => {
168
+ const data = new Float32Array(UNIFORM_STRIDE / 4);
169
+ data.set(viewProj, 0);
170
+ data.set(item.model, 16);
171
+ data.set([frame.light.toLight.x, frame.light.toLight.y, frame.light.toLight.z, 0], 32);
172
+ data.set([frame.light.intensity, frame.light.ambient, 0, 0], 36);
173
+ this.device.queue.writeBuffer(this.uniformBuffer, i * UNIFORM_STRIDE, data);
174
+ });
175
+ const encoder = this.device.createCommandEncoder();
176
+ const pass = encoder.beginRenderPass({
177
+ colorAttachments: [
178
+ {
179
+ view: this.context.getCurrentTexture().createView(),
180
+ clearValue: this.clearValue,
181
+ loadOp: "clear",
182
+ storeOp: "store",
183
+ },
184
+ ],
185
+ depthStencilAttachment: {
186
+ view: this.depthTexture.createView(),
187
+ depthClearValue: 1,
188
+ depthLoadOp: "clear",
189
+ depthStoreOp: "store",
190
+ },
191
+ });
192
+ pass.setPipeline(this.pipeline);
193
+ frame.items.forEach((item, i) => {
194
+ const mesh = this.buffers(item.mesh);
195
+ pass.setBindGroup(0, bindGroup, [i * UNIFORM_STRIDE]);
196
+ pass.setVertexBuffer(0, mesh.position);
197
+ pass.setVertexBuffer(1, mesh.normal);
198
+ pass.setVertexBuffer(2, mesh.color);
199
+ pass.draw(mesh.count);
200
+ });
201
+ pass.end();
202
+ this.device.queue.submit([encoder.finish()]);
203
+ }
204
+ destroy() {
205
+ this.destroyed = true;
206
+ for (const mesh of this.cache.values()) {
207
+ mesh.position.destroy?.();
208
+ mesh.normal.destroy?.();
209
+ mesh.color.destroy?.();
210
+ }
211
+ this.cache.clear();
212
+ this.uniformBuffer?.destroy?.();
213
+ this.depthTexture?.destroy?.();
214
+ this.device?.destroy?.();
215
+ this.device = undefined;
216
+ this.context = undefined;
217
+ this.pipeline = undefined;
218
+ this.uniformBuffer = undefined;
219
+ this.depthTexture = undefined;
220
+ this.canvas = undefined;
221
+ }
222
+ }
@@ -0,0 +1,11 @@
1
+ import type { Mesh } from "../core/mesh.js";
2
+ /**
3
+ * Build a cube-sphere (spherified cube) centered on the origin: each cube face
4
+ * is gridded and projected onto the sphere. Even, all-quad, no poles.
5
+ *
6
+ * @param size Diameter. Defaults to `1`.
7
+ * @param detail Subdivisions per cube face edge, `1` = simplest (6 quads).
8
+ * Defaults to `1`.
9
+ * @param colors CSS colors cycled across faces. Defaults to a built-in palette.
10
+ */
11
+ export declare function cubeSphere(size?: number, detail?: number, colors?: string[]): Mesh;
@@ -0,0 +1,50 @@
1
+ const DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
2
+ // right x up == normal for each face, so generated quads wind CCW outward.
3
+ const CUBE_FACES = [
4
+ { normal: [0, 0, 1], right: [1, 0, 0], up: [0, 1, 0] },
5
+ { normal: [0, 0, -1], right: [-1, 0, 0], up: [0, 1, 0] },
6
+ { normal: [1, 0, 0], right: [0, 0, -1], up: [0, 1, 0] },
7
+ { normal: [-1, 0, 0], right: [0, 0, 1], up: [0, 1, 0] },
8
+ { normal: [0, 1, 0], right: [1, 0, 0], up: [0, 0, -1] },
9
+ { normal: [0, -1, 0], right: [1, 0, 0], up: [0, 0, 1] },
10
+ ];
11
+ /**
12
+ * Build a cube-sphere (spherified cube) centered on the origin: each cube face
13
+ * is gridded and projected onto the sphere. Even, all-quad, no poles.
14
+ *
15
+ * @param size Diameter. Defaults to `1`.
16
+ * @param detail Subdivisions per cube face edge, `1` = simplest (6 quads).
17
+ * Defaults to `1`.
18
+ * @param colors CSS colors cycled across faces. Defaults to a built-in palette.
19
+ */
20
+ export function cubeSphere(size = 1, detail = 1, colors = DEFAULT_COLORS) {
21
+ const r = size / 2;
22
+ const n = Math.max(1, Math.floor(detail));
23
+ const vertices = [];
24
+ const faces = [];
25
+ let ci = 0;
26
+ for (const face of CUBE_FACES) {
27
+ const base = vertices.length;
28
+ for (let i = 0; i <= n; i++) {
29
+ for (let j = 0; j <= n; j++) {
30
+ const u = -1 + (2 * i) / n;
31
+ const v = -1 + (2 * j) / n;
32
+ const x = face.normal[0] + u * face.right[0] + v * face.up[0];
33
+ const y = face.normal[1] + u * face.right[1] + v * face.up[1];
34
+ const z = face.normal[2] + u * face.right[2] + v * face.up[2];
35
+ const len = Math.hypot(x, y, z);
36
+ vertices.push({ x: (x / len) * r, y: (y / len) * r, z: (z / len) * r });
37
+ }
38
+ }
39
+ const idx = (i, j) => base + i * (n + 1) + j;
40
+ for (let i = 0; i < n; i++) {
41
+ for (let j = 0; j < n; j++) {
42
+ faces.push({
43
+ indices: [idx(i, j), idx(i + 1, j), idx(i + 1, j + 1), idx(i, j + 1)],
44
+ color: colors[ci++ % colors.length],
45
+ });
46
+ }
47
+ }
48
+ }
49
+ return { vertices, faces };
50
+ }
@@ -0,0 +1,9 @@
1
+ import type { Mesh } from "../core/mesh.js";
2
+ /**
3
+ * Build a cube mesh centered on the origin.
4
+ *
5
+ * @param size Edge length. Defaults to `1`.
6
+ * @param colors Six CSS colors, one per face (front, back, top, bottom, right, left).
7
+ * Defaults to a built-in palette.
8
+ */
9
+ export declare function cube(size?: number, colors?: string[]): Mesh;
@@ -0,0 +1,30 @@
1
+ const DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
2
+ /**
3
+ * Build a cube mesh centered on the origin.
4
+ *
5
+ * @param size Edge length. Defaults to `1`.
6
+ * @param colors Six CSS colors, one per face (front, back, top, bottom, right, left).
7
+ * Defaults to a built-in palette.
8
+ */
9
+ export function cube(size = 1, colors = DEFAULT_COLORS) {
10
+ const h = size / 2;
11
+ const vertices = [
12
+ { x: -h, y: -h, z: h },
13
+ { x: h, y: -h, z: h },
14
+ { x: h, y: h, z: h },
15
+ { x: -h, y: h, z: h },
16
+ { x: -h, y: -h, z: -h },
17
+ { x: h, y: -h, z: -h },
18
+ { x: h, y: h, z: -h },
19
+ { x: -h, y: h, z: -h },
20
+ ];
21
+ const faces = [
22
+ { indices: [0, 1, 2, 3], color: colors[0 % colors.length] },
23
+ { indices: [5, 4, 7, 6], color: colors[1 % colors.length] },
24
+ { indices: [3, 2, 6, 7], color: colors[2 % colors.length] },
25
+ { indices: [4, 5, 1, 0], color: colors[3 % colors.length] },
26
+ { indices: [1, 5, 6, 2], color: colors[4 % colors.length] },
27
+ { indices: [4, 0, 3, 7], color: colors[5 % colors.length] },
28
+ ];
29
+ return { vertices, faces };
30
+ }
@@ -0,0 +1,11 @@
1
+ import type { Mesh } from "../core/mesh.js";
2
+ /**
3
+ * Build an icosphere (subdivided icosahedron) centered on the origin. Gives the
4
+ * most uniform triangle distribution of the sphere types.
5
+ *
6
+ * @param size Diameter. Defaults to `1`.
7
+ * @param detail Subdivision level, `1` = base icosahedron (20 faces). Each level
8
+ * splits every triangle into four. Defaults to `1`.
9
+ * @param colors CSS colors cycled across faces. Defaults to a built-in palette.
10
+ */
11
+ export declare function icosphere(size?: number, detail?: number, colors?: string[]): Mesh;
@@ -0,0 +1,51 @@
1
+ import { sphereFromTriangles } from "../core/geometry.js";
2
+ const DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
3
+ const T = (1 + Math.sqrt(5)) / 2;
4
+ const SEED_VERTICES = [
5
+ { x: -1, y: T, z: 0 },
6
+ { x: 1, y: T, z: 0 },
7
+ { x: -1, y: -T, z: 0 },
8
+ { x: 1, y: -T, z: 0 },
9
+ { x: 0, y: -1, z: T },
10
+ { x: 0, y: 1, z: T },
11
+ { x: 0, y: -1, z: -T },
12
+ { x: 0, y: 1, z: -T },
13
+ { x: T, y: 0, z: -1 },
14
+ { x: T, y: 0, z: 1 },
15
+ { x: -T, y: 0, z: -1 },
16
+ { x: -T, y: 0, z: 1 },
17
+ ];
18
+ const SEED_FACES = [
19
+ [0, 11, 5],
20
+ [0, 5, 1],
21
+ [0, 1, 7],
22
+ [0, 7, 10],
23
+ [0, 10, 11],
24
+ [1, 5, 9],
25
+ [5, 11, 4],
26
+ [11, 10, 2],
27
+ [10, 7, 6],
28
+ [7, 1, 8],
29
+ [3, 9, 4],
30
+ [3, 4, 2],
31
+ [3, 2, 6],
32
+ [3, 6, 8],
33
+ [3, 8, 9],
34
+ [4, 9, 5],
35
+ [2, 4, 11],
36
+ [6, 2, 10],
37
+ [8, 6, 7],
38
+ [9, 8, 1],
39
+ ];
40
+ /**
41
+ * Build an icosphere (subdivided icosahedron) centered on the origin. Gives the
42
+ * most uniform triangle distribution of the sphere types.
43
+ *
44
+ * @param size Diameter. Defaults to `1`.
45
+ * @param detail Subdivision level, `1` = base icosahedron (20 faces). Each level
46
+ * splits every triangle into four. Defaults to `1`.
47
+ * @param colors CSS colors cycled across faces. Defaults to a built-in palette.
48
+ */
49
+ export function icosphere(size = 1, detail = 1, colors = DEFAULT_COLORS) {
50
+ return sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors);
51
+ }
@@ -0,0 +1,10 @@
1
+ import type { Mesh } from "../core/mesh.js";
2
+ /**
3
+ * Build an octa-sphere (subdivided octahedron) centered on the origin.
4
+ *
5
+ * @param size Diameter. Defaults to `1`.
6
+ * @param detail Subdivision level, `1` = base octahedron (8 faces). Each level
7
+ * splits every triangle into four. Defaults to `1`.
8
+ * @param colors CSS colors cycled across faces. Defaults to a built-in palette.
9
+ */
10
+ export declare function octaSphere(size?: number, detail?: number, colors?: string[]): Mesh;
@@ -0,0 +1,31 @@
1
+ import { sphereFromTriangles } from "../core/geometry.js";
2
+ const DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
3
+ const SEED_VERTICES = [
4
+ { x: 1, y: 0, z: 0 },
5
+ { x: -1, y: 0, z: 0 },
6
+ { x: 0, y: 1, z: 0 },
7
+ { x: 0, y: -1, z: 0 },
8
+ { x: 0, y: 0, z: 1 },
9
+ { x: 0, y: 0, z: -1 },
10
+ ];
11
+ const SEED_FACES = [
12
+ [4, 0, 2],
13
+ [4, 2, 1],
14
+ [4, 1, 3],
15
+ [4, 3, 0],
16
+ [5, 2, 0],
17
+ [5, 1, 2],
18
+ [5, 3, 1],
19
+ [5, 0, 3],
20
+ ];
21
+ /**
22
+ * Build an octa-sphere (subdivided octahedron) centered on the origin.
23
+ *
24
+ * @param size Diameter. Defaults to `1`.
25
+ * @param detail Subdivision level, `1` = base octahedron (8 faces). Each level
26
+ * splits every triangle into four. Defaults to `1`.
27
+ * @param colors CSS colors cycled across faces. Defaults to a built-in palette.
28
+ */
29
+ export function octaSphere(size = 1, detail = 1, colors = DEFAULT_COLORS) {
30
+ return sphereFromTriangles(SEED_VERTICES, SEED_FACES, size, detail, colors);
31
+ }
@@ -0,0 +1,8 @@
1
+ import type { Mesh } from "../core/mesh.js";
2
+ /**
3
+ * Build a regular octahedron mesh centered on the origin.
4
+ *
5
+ * @param size Distance between opposite vertices. Defaults to `1`.
6
+ * @param colors Eight CSS colors, one per triangular face. Defaults to a built-in palette.
7
+ */
8
+ export declare function octahedron(size?: number, colors?: string[]): Mesh;
@@ -0,0 +1,38 @@
1
+ const DEFAULT_COLORS = [
2
+ "#3b82f6",
3
+ "#8b5cf6",
4
+ "#ec4899",
5
+ "#f59e0b",
6
+ "#10b981",
7
+ "#ef4444",
8
+ "#06b6d4",
9
+ "#eab308",
10
+ ];
11
+ /**
12
+ * Build a regular octahedron mesh centered on the origin.
13
+ *
14
+ * @param size Distance between opposite vertices. Defaults to `1`.
15
+ * @param colors Eight CSS colors, one per triangular face. Defaults to a built-in palette.
16
+ */
17
+ export function octahedron(size = 1, colors = DEFAULT_COLORS) {
18
+ const r = size / 2;
19
+ const vertices = [
20
+ { x: r, y: 0, z: 0 },
21
+ { x: -r, y: 0, z: 0 },
22
+ { x: 0, y: r, z: 0 },
23
+ { x: 0, y: -r, z: 0 },
24
+ { x: 0, y: 0, z: r },
25
+ { x: 0, y: 0, z: -r },
26
+ ];
27
+ const faces = [
28
+ { indices: [4, 0, 2], color: colors[0 % colors.length] },
29
+ { indices: [4, 2, 1], color: colors[1 % colors.length] },
30
+ { indices: [4, 1, 3], color: colors[2 % colors.length] },
31
+ { indices: [4, 3, 0], color: colors[3 % colors.length] },
32
+ { indices: [5, 2, 0], color: colors[4 % colors.length] },
33
+ { indices: [5, 1, 2], color: colors[5 % colors.length] },
34
+ { indices: [5, 3, 1], color: colors[6 % colors.length] },
35
+ { indices: [5, 0, 3], color: colors[7 % colors.length] },
36
+ ];
37
+ return { vertices, faces };
38
+ }
@@ -0,0 +1,9 @@
1
+ import type { Mesh } from "../core/mesh.js";
2
+ /**
3
+ * Build a square pyramid mesh centered on the origin, base down.
4
+ *
5
+ * @param size Base edge length (also the height). Defaults to `1`.
6
+ * @param colors Five CSS colors: the square base, then the four triangular sides.
7
+ * Defaults to a built-in palette.
8
+ */
9
+ export declare function pyramid(size?: number, colors?: string[]): Mesh;
@@ -0,0 +1,26 @@
1
+ const DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981"];
2
+ /**
3
+ * Build a square pyramid mesh centered on the origin, base down.
4
+ *
5
+ * @param size Base edge length (also the height). Defaults to `1`.
6
+ * @param colors Five CSS colors: the square base, then the four triangular sides.
7
+ * Defaults to a built-in palette.
8
+ */
9
+ export function pyramid(size = 1, colors = DEFAULT_COLORS) {
10
+ const h = size / 2;
11
+ const vertices = [
12
+ { x: -h, y: -h, z: h },
13
+ { x: h, y: -h, z: h },
14
+ { x: h, y: -h, z: -h },
15
+ { x: -h, y: -h, z: -h },
16
+ { x: 0, y: h, z: 0 },
17
+ ];
18
+ const faces = [
19
+ { indices: [0, 3, 2, 1], color: colors[0 % colors.length] },
20
+ { indices: [4, 0, 1], color: colors[1 % colors.length] },
21
+ { indices: [4, 1, 2], color: colors[2 % colors.length] },
22
+ { indices: [4, 2, 3], color: colors[3 % colors.length] },
23
+ { indices: [4, 3, 0], color: colors[4 % colors.length] },
24
+ ];
25
+ return { vertices, faces };
26
+ }
@@ -0,0 +1,8 @@
1
+ import type { Mesh } from "../core/mesh.js";
2
+ /**
3
+ * Build a regular tetrahedron mesh centered on the origin.
4
+ *
5
+ * @param size Approximate diameter. Defaults to `1`.
6
+ * @param colors Four CSS colors, one per triangular face. Defaults to a built-in palette.
7
+ */
8
+ export declare function tetrahedron(size?: number, colors?: string[]): Mesh;
@@ -0,0 +1,23 @@
1
+ const DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b"];
2
+ /**
3
+ * Build a regular tetrahedron mesh centered on the origin.
4
+ *
5
+ * @param size Approximate diameter. Defaults to `1`.
6
+ * @param colors Four CSS colors, one per triangular face. Defaults to a built-in palette.
7
+ */
8
+ export function tetrahedron(size = 1, colors = DEFAULT_COLORS) {
9
+ const s = size / 2;
10
+ const vertices = [
11
+ { x: s, y: s, z: s },
12
+ { x: s, y: -s, z: -s },
13
+ { x: -s, y: s, z: -s },
14
+ { x: -s, y: -s, z: s },
15
+ ];
16
+ const faces = [
17
+ { indices: [0, 1, 2], color: colors[0 % colors.length] },
18
+ { indices: [0, 3, 1], color: colors[1 % colors.length] },
19
+ { indices: [0, 2, 3], color: colors[2 % colors.length] },
20
+ { indices: [1, 3, 2], color: colors[3 % colors.length] },
21
+ ];
22
+ return { vertices, faces };
23
+ }
@@ -0,0 +1,10 @@
1
+ import type { Mesh } from "../core/mesh.js";
2
+ /**
3
+ * Build a UV (latitude/longitude) sphere centered on the origin.
4
+ *
5
+ * @param size Diameter. Defaults to `1`.
6
+ * @param detail Tessellation level, `1` = simplest. Higher values add rings and
7
+ * segments. Defaults to `1`.
8
+ * @param colors CSS colors cycled across faces. Defaults to a built-in palette.
9
+ */
10
+ export declare function uvSphere(size?: number, detail?: number, colors?: string[]): Mesh;
@@ -0,0 +1,50 @@
1
+ const DEFAULT_COLORS = ["#3b82f6", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#ef4444"];
2
+ /**
3
+ * Build a UV (latitude/longitude) sphere centered on the origin.
4
+ *
5
+ * @param size Diameter. Defaults to `1`.
6
+ * @param detail Tessellation level, `1` = simplest. Higher values add rings and
7
+ * segments. Defaults to `1`.
8
+ * @param colors CSS colors cycled across faces. Defaults to a built-in palette.
9
+ */
10
+ export function uvSphere(size = 1, detail = 1, colors = DEFAULT_COLORS) {
11
+ const r = size / 2;
12
+ const d = Math.max(1, Math.floor(detail));
13
+ const slices = Math.max(4, d * 4);
14
+ const stacks = Math.max(2, d * 2);
15
+ const vertices = [{ x: 0, y: r, z: 0 }];
16
+ const ring = (i, j) => 1 + (i - 1) * slices + j;
17
+ for (let i = 1; i < stacks; i++) {
18
+ const phi = (Math.PI * i) / stacks;
19
+ const y = r * Math.cos(phi);
20
+ const rr = r * Math.sin(phi);
21
+ for (let j = 0; j < slices; j++) {
22
+ const theta = (2 * Math.PI * j) / slices;
23
+ vertices.push({ x: rr * Math.cos(theta), y, z: rr * Math.sin(theta) });
24
+ }
25
+ }
26
+ const bottom = vertices.length;
27
+ vertices.push({ x: 0, y: -r, z: 0 });
28
+ const faces = [];
29
+ let ci = 0;
30
+ const color = () => colors[ci++ % colors.length];
31
+ for (let j = 0; j < slices; j++) {
32
+ faces.push({ indices: [0, ring(1, (j + 1) % slices), ring(1, j)], color: color() });
33
+ }
34
+ for (let i = 1; i < stacks - 1; i++) {
35
+ for (let j = 0; j < slices; j++) {
36
+ const j1 = (j + 1) % slices;
37
+ faces.push({
38
+ indices: [ring(i, j), ring(i, j1), ring(i + 1, j1), ring(i + 1, j)],
39
+ color: color(),
40
+ });
41
+ }
42
+ }
43
+ for (let j = 0; j < slices; j++) {
44
+ faces.push({
45
+ indices: [bottom, ring(stacks - 1, j), ring(stacks - 1, (j + 1) % slices)],
46
+ color: color(),
47
+ });
48
+ }
49
+ return { vertices, faces };
50
+ }
@@ -0,0 +1,39 @@
1
+ export type EaseType = "linear" | "quadratic" | "cubic" | "quartic" | "quintic" | "easeInSine" | "easeOutSine" | "easeInOutSine" | "easeInQuad" | "easeOutQuad" | "easeInOutQuad" | "easeInCubic" | "easeOutCubic" | "easeInOutCubic" | "easeInQuart" | "easeOutQuart" | "easeInOutQuart" | "easeInQuint" | "easeOutQuint" | "easeInOutQuint" | "easeInExpo" | "easeOutExpo" | "easeInOutExpo" | "easeInCirc" | "easeOutCirc" | "easeInOutCirc" | "easeInBack" | "easeOutBack" | "easeInOutBack" | "easeInElastic" | "easeOutElastic" | "easeInOutElastic" | "easeInBounce" | "easeOutBounce" | "easeInOutBounce";
2
+ export type EaseFunction = (value: number, overextend?: boolean) => number;
3
+ export declare function linear(value: number, overextend?: boolean): number;
4
+ export declare function quadratic(value: number, overextend?: boolean): number;
5
+ export declare function cubic(value: number, overextend?: boolean): number;
6
+ export declare function quartic(value: number, overextend?: boolean): number;
7
+ export declare function quintic(value: number, overextend?: boolean): number;
8
+ export declare function easeInSine(value: number, overextend?: boolean): number;
9
+ export declare function easeOutSine(value: number, overextend?: boolean): number;
10
+ export declare function easeInOutSine(value: number, overextend?: boolean): number;
11
+ export declare function easeInQuad(value: number, overextend?: boolean): number;
12
+ export declare function easeOutQuad(value: number, overextend?: boolean): number;
13
+ export declare function easeInOutQuad(value: number, overextend?: boolean): number;
14
+ export declare function easeInCubic(value: number, overextend?: boolean): number;
15
+ export declare function easeOutCubic(value: number, overextend?: boolean): number;
16
+ export declare function easeInOutCubic(value: number, overextend?: boolean): number;
17
+ export declare function easeInQuart(value: number, overextend?: boolean): number;
18
+ export declare function easeOutQuart(value: number, overextend?: boolean): number;
19
+ export declare function easeInOutQuart(value: number, overextend?: boolean): number;
20
+ export declare function easeInQuint(value: number, overextend?: boolean): number;
21
+ export declare function easeOutQuint(value: number, overextend?: boolean): number;
22
+ export declare function easeInOutQuint(value: number, overextend?: boolean): number;
23
+ export declare function easeInExpo(value: number, overextend?: boolean): number;
24
+ export declare function easeOutExpo(value: number, overextend?: boolean): number;
25
+ export declare function easeInOutExpo(value: number, overextend?: boolean): number;
26
+ export declare function easeInCirc(value: number, overextend?: boolean): number;
27
+ export declare function easeOutCirc(value: number, overextend?: boolean): number;
28
+ export declare function easeInOutCirc(value: number, overextend?: boolean): number;
29
+ export declare function easeInBack(value: number, overextend?: boolean): number;
30
+ export declare function easeOutBack(value: number, overextend?: boolean): number;
31
+ export declare function easeInOutBack(value: number, overextend?: boolean): number;
32
+ export declare function easeInElastic(value: number, overextend?: boolean): number;
33
+ export declare function easeOutElastic(value: number, overextend?: boolean): number;
34
+ export declare function easeInOutElastic(value: number, overextend?: boolean): number;
35
+ export declare function easeInBounce(value: number, overextend?: boolean): number;
36
+ export declare function easeOutBounce(value: number, overextend?: boolean): number;
37
+ export declare function easeInOutBounce(value: number, overextend?: boolean): number;
38
+ export declare const easeTypes: Readonly<Record<EaseType, EaseFunction>>;
39
+ export declare function ease(type: EaseType, value: number, overextend?: boolean): number;