trid3d 0.0.0-stage → 0.1.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.
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Creates a cube centered at origin.
3
+ * @param {number} size - full width/height/depth
4
+ * @returns {{ vertices: {x:number,y:number,z:number}[], edges: number[][] }}
5
+ */
6
+ function cubeWire(size = 0.5) {
7
+ return rectPrismWire(size, size, size);
8
+ }
9
+
10
+ /**
11
+ * Creates a rectangular prism (box) centered at origin.
12
+ * @param {number} w - width (x)
13
+ * @param {number} h - height (y)
14
+ * @param {number} d - depth (z)
15
+ */
16
+ function rectPrismWire(w = 0.6, h = 0.3, d = 0.4) {
17
+ const x = w / 2, y = h / 2, z = d / 2;
18
+
19
+ const mesh = {
20
+ vertices: [
21
+ { x: x, y: y, z: z },
22
+ { x: -x, y: y, z: z },
23
+ { x: -x, y: -y, z: z },
24
+ { x: x, y: -y, z: z },
25
+
26
+ { x: x, y: y, z: -z },
27
+ { x: -x, y: y, z: -z },
28
+ { x: -x, y: -y, z: -z },
29
+ { x: x, y: -y, z: -z },
30
+ ],
31
+ edges: [
32
+ [0, 1, 2, 3],
33
+ [4, 5, 6, 7],
34
+ [0, 4],
35
+ [1, 5],
36
+ [2, 6],
37
+ [3, 7],
38
+ ],
39
+ // Clockwise as seen from outside
40
+ faces: [
41
+ [4, 7, 6, 5], // front (-z)
42
+ [0, 1, 2, 3], // back (+z)
43
+ [0, 4, 5, 1], // top
44
+ [3, 2, 6, 7], // bottom
45
+ [0, 3, 7, 4], // right
46
+ [1, 5, 6, 2], // left
47
+ ],
48
+ };
49
+ return withFaceUVs(mesh);
50
+ }
51
+
52
+ /**
53
+ * Gives every face the whole texture, upright as seen from outside:
54
+ * side faces have +Y up, the top face has +Z up, the bottom face -Z.
55
+ */
56
+ function withFaceUVs(mesh) {
57
+ const uvs = [], faceUVs = [];
58
+ for (const f of mesh.faces) {
59
+ const p = f.map((i) => mesh.vertices[i]);
60
+ // Outward normal (faces are clockwise from outside)
61
+ let nx = 0, ny = 0, nz = 0;
62
+ for (let i = 0; i < p.length; i++) {
63
+ const a = p[i], b = p[(i + 1) % p.length];
64
+ nx += (a.y - b.y) * (a.z + b.z);
65
+ ny += (a.z - b.z) * (a.x + b.x);
66
+ nz += (a.x - b.x) * (a.y + b.y);
67
+ }
68
+ const up = Math.abs(ny) > Math.max(Math.abs(nx), Math.abs(nz))
69
+ ? { x: 0, y: 0, z: ny > 0 ? 1 : -1 }
70
+ : { x: 0, y: 1, z: 0 };
71
+ // Right, as seen from outside: normal × up
72
+ const right = { x: ny * up.z - nz * up.y, y: nz * up.x - nx * up.z, z: nx * up.y - ny * up.x };
73
+ const us = p.map((q) => q.x * right.x + q.y * right.y + q.z * right.z);
74
+ const vs = p.map((q) => q.x * up.x + q.y * up.y + q.z * up.z);
75
+ const u0 = Math.min(...us), u1 = Math.max(...us), v0 = Math.min(...vs), v1 = Math.max(...vs);
76
+ faceUVs.push(p.map((_, k) => {
77
+ uvs.push({ u: (us[k] - u0) / (u1 - u0 || 1), v: (vs[k] - v0) / (v1 - v0 || 1) });
78
+ return uvs.length - 1;
79
+ }));
80
+ }
81
+ mesh.uvs = uvs;
82
+ mesh.faceUVs = faceUVs;
83
+ return mesh;
84
+ }
85
+
86
+ /**
87
+ * Wireframe sphere made of latitude/longitude lines.
88
+ * (Not triangles yet, just lines.)
89
+ *
90
+ * Each pole is a single vertex, so `rings` latitude bands produce
91
+ * `rings - 1` drawn circles between the poles.
92
+ *
93
+ * @param {number} radius
94
+ * @param {number} rings - latitude bands from pole to pole (min 2)
95
+ * @param {number} segments - points per circle
96
+ */
97
+ function sphereWire(radius = 0.35, rings = 10, segments = 16) {
98
+ rings = Math.max(2, rings);
99
+
100
+ const bottom = 0;
101
+ const vertices = [{ x: 0, y: -radius, z: 0 }];
102
+ const edges = [];
103
+
104
+ for (let r = 1; r < rings; r++) {
105
+ const v = r / rings;
106
+ const phi = (v * Math.PI) - Math.PI/2;
107
+
108
+ const y = Math.sin(phi) * radius;
109
+ const ringRadius = Math.cos(phi) * radius;
110
+
111
+ const startIndex = vertices.length;
112
+
113
+ for (let s = 0; s < segments; s++) {
114
+ const u = s / segments;
115
+ const theta = u * Math.PI * 2;
116
+
117
+ const x = Math.cos(theta) * ringRadius;
118
+ const z = Math.sin(theta) * ringRadius;
119
+
120
+ vertices.push({ x, y, z });
121
+ }
122
+
123
+ edges.push(Array.from({ length: segments }, (_, i) => startIndex + i));
124
+ }
125
+
126
+ const top = vertices.length;
127
+ vertices.push({ x: 0, y: radius, z: 0 });
128
+
129
+ // Meridians: bottom pole -> each ring -> top pole
130
+ const lastRing = 1 + (rings - 2) * segments;
131
+ for (let s = 0; s < segments; s++) {
132
+ edges.push([bottom, 1 + s]);
133
+ for (let r = 0; r < rings - 2; r++) {
134
+ const ringA = 1 + r * segments;
135
+ const ringB = ringA + segments;
136
+ edges.push([ringA + s, ringB + s]);
137
+ }
138
+ edges.push([lastRing + s, top]);
139
+ }
140
+
141
+ // Faces, clockwise as seen from outside: triangles at the poles, quads between rings.
142
+ // Texture coordinates wrap around like a world map: u around, v from bottom to top.
143
+ // The seam gets its own u = 1 coordinates, so the image doesn't smear across it.
144
+ const faces = [];
145
+ const uvs = [];
146
+ const faceUVs = [];
147
+ const uv = (s, r) => uvs.push({ u: s / segments, v: r / rings }) - 1;
148
+ for (let s = 0; s < segments; s++) {
149
+ const s1 = (s + 1) % segments;
150
+ faces.push([bottom, 1 + s, 1 + s1]);
151
+ faceUVs.push([uv(s + 0.5, 0), uv(s, 1), uv(s + 1, 1)]);
152
+ for (let r = 0; r < rings - 2; r++) {
153
+ const a = 1 + r * segments, b = a + segments;
154
+ faces.push([a + s, b + s, b + s1, a + s1]);
155
+ faceUVs.push([uv(s, r + 1), uv(s, r + 2), uv(s + 1, r + 2), uv(s + 1, r + 1)]);
156
+ }
157
+ faces.push([top, lastRing + s1, lastRing + s]);
158
+ faceUVs.push([uv(s + 0.5, rings), uv(s + 1, rings - 1), uv(s, rings - 1)]);
159
+ }
160
+
161
+ return { vertices, edges, faces, uvs, faceUVs };
162
+ }
163
+
164
+ /**
165
+ * Scales a mesh along the X, Y, and Z axes.
166
+ *
167
+ * This function modifies the mesh **in place** by multiplying
168
+ * each vertex coordinate by the given scale factors.
169
+ *
170
+ * Common use cases:
171
+ * - Change width / height / depth of a box
172
+ * - Stretch a sphere into an ellipsoid
173
+ * - Apply non-uniform scaling to any mesh
174
+ *
175
+ * NOTE:
176
+ * - This affects only vertex positions
177
+ * - Edge indices remain unchanged
178
+ *
179
+ * @param {{ vertices: {x:number,y:number,z:number}[] }} mesh - Mesh to scale
180
+ * @param {number} sx - Scale factor along X axis (default: 1)
181
+ * @param {number} sy - Scale factor along Y axis (default: 1)
182
+ * @param {number} sz - Scale factor along Z axis (default: 1)
183
+ * @returns The same mesh instance after scaling
184
+ */
185
+ export function scaleMesh(mesh, sx = 1, sy = 1, sz = 1) {
186
+ for (const v of mesh.vertices) {
187
+ v.x *= sx;
188
+ v.y *= sy;
189
+ v.z *= sz;
190
+ }
191
+ return mesh;
192
+ }
193
+
194
+ /**
195
+ * Factory function that creates a predefined wireframe shape.
196
+ *
197
+ * This provides a unified API for constructing different meshes
198
+ * (cube, box, sphere) while keeping the rendering pipeline generic.
199
+ *
200
+ * Supported shapes:
201
+ * - "cube" → wireframe cube
202
+ * - "box" → rectangular prism (box)
203
+ * - "sphere" → wireframe sphere
204
+ *
205
+ * Each shape type accepts its own configuration options via `opts`.
206
+ * Unspecified options fall back to sensible defaults.
207
+ *
208
+ * @param {string} kind - Shape type identifier
209
+ * @param {Object} opts - Shape configuration options
210
+ * @param {number} [opts.size] - Cube size
211
+ * @param {number} [opts.w] - Box width
212
+ * @param {number} [opts.h] - Box height
213
+ * @param {number} [opts.d] - Box depth
214
+ * @param {number} [opts.r] - Sphere radius
215
+ * @param {number} [opts.rings] - Number of latitude rings (sphere)
216
+ * @param {number} [opts.segments] - Segments per ring (sphere)
217
+ *
218
+ * @returns {{ vertices: {x:number,y:number,z:number}[], edges: number[][] }}
219
+ * A wireframe mesh object
220
+ *
221
+ * @throws {Error} If the shape type is not supported
222
+ */
223
+ export function makeShape(kind, opts = {}) {
224
+ switch (kind.toLowerCase()) {
225
+ case "cube": return cubeWire(opts.size ?? 0.5);
226
+ case "box": return rectPrismWire(opts.w ?? 0.6, opts.h ?? 0.6, opts.d ?? 0.6);
227
+ case "sphere":return sphereWire(opts.r ?? 0.45, opts.rings ?? 12, opts.segments ?? 24);
228
+ default: throw new Error("Unknown shape: " + kind);
229
+ }
230
+ }