@carbonenginejs/runtime-utils 0.1.1 → 0.1.3
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/LICENSE +21 -21
- package/NOTICE +23 -23
- package/README.md +69 -69
- package/THIRD-PARTY-NOTICES.md +64 -64
- package/docs/README.md +74 -73
- package/docs/architecture.md +101 -101
- package/docs/concepts/foundation-consolidation.md +85 -85
- package/docs/concepts/model-lifecycle.md +111 -0
- package/docs/const-kb.md +85 -87
- package/docs/core-types/DECORATOR-TODOS.md +25 -25
- package/docs/core-types/README.md +229 -168
- package/docs/reference/api.md +105 -102
- package/docs/reference/classes/README.md +135 -135
- package/package.json +139 -139
- package/src/arrays.js +5 -5
- package/src/audio/audioFormats.js +34 -34
- package/src/audio/index.js +1 -1
- package/src/box3.js +331 -331
- package/src/bytes.js +56 -56
- package/src/compression.js +56 -56
- package/src/constants/index.js +7 -7
- package/src/constants/trinity.js +13 -13
- package/src/constants.js +15 -15
- package/src/curve.js +79 -79
- package/src/d3d/dxgiFormats.js +46 -46
- package/src/d3d/index.js +2 -2
- package/src/d3d/primitiveTopology.js +11 -11
- package/src/document/CjsCarbonDocument.js +212 -212
- package/src/document/CjsClassRegistry.js +373 -373
- package/src/document/CjsDocumentDehydrator.js +142 -142
- package/src/document/CjsDocumentHydrator.js +156 -156
- package/src/document/CjsStructRegistry.js +348 -348
- package/src/document/hydrationAdapter.js +129 -129
- package/src/document/index.js +6 -6
- package/src/errors/CjsError.js +286 -286
- package/src/errors/index.js +5 -5
- package/src/geometry/box.js +22 -22
- package/src/geometry/cylinder.js +22 -22
- package/src/geometry/helpers/earcut.js +1 -1
- package/src/geometry/helpers/misc.js +3 -3
- package/src/geometry/index.js +8 -8
- package/src/geometry/json.js +67 -67
- package/src/geometry/lathe.js +44 -44
- package/src/geometry/plane.js +14 -14
- package/src/geometry/shape.js +4 -4
- package/src/geometry/sphere.js +24 -24
- package/src/geometry/torus.js +14 -14
- package/src/graphics/colorSpaces.js +22 -22
- package/src/graphics/index.js +4 -4
- package/src/graphics/pixelFormats.js +158 -158
- package/src/graphics/textureDimensions.js +22 -22
- package/src/graphics/trinityEnums.js +87 -87
- package/src/index.js +62 -62
- package/src/is.js +108 -46
- package/src/json.js +23 -23
- package/src/lifecycle/CjsLifecycleState.js +77 -77
- package/src/lifecycle/index.js +1 -1
- package/src/lne3.js +70 -70
- package/src/lookup.js +48 -48
- package/src/mat3.js +51 -51
- package/src/mat4.js +699 -699
- package/src/math/index.js +25 -25
- package/src/math/scalar.js +63 -63
- package/src/media/index.js +1 -1
- package/src/media/mediaTypes.js +50 -50
- package/src/mesh.js +424 -424
- package/src/model/CjsEventEmitter.js +333 -333
- package/src/model/CjsModel.js +1589 -1544
- package/src/model/CjsModelState.js +72 -72
- package/src/model/index.js +4 -4
- package/src/model/sourceRecordUtils.js +54 -54
- package/src/noise.js +310 -310
- package/src/num.js +827 -827
- package/src/object.js +39 -39
- package/src/path.js +53 -53
- package/src/pln.js +125 -125
- package/src/pool.js +9 -9
- package/src/quat.js +144 -144
- package/src/ray3.js +188 -188
- package/src/renderContext/formats.js +145 -145
- package/src/renderContext/index.js +5 -5
- package/src/renderContext/presentation.js +125 -125
- package/src/renderContext/resources.js +27 -27
- package/src/renderContext/upscaling.js +22 -22
- package/src/renderContext/window.js +20 -20
- package/src/runtime/CjsRuntimeState.js +50 -50
- package/src/schema/CjsSchema.js +555 -318
- package/src/schema/index.js +4 -4
- package/src/shader/index.js +1 -1
- package/src/shader/shaderStages.js +37 -37
- package/src/sph3.js +181 -181
- package/src/tangent.js +288 -288
- package/src/text.js +40 -40
- package/src/tri3.js +98 -98
- package/src/types/carbonTypes.js +635 -635
- package/src/types/index.js +2 -2
- package/src/utils.js +58 -58
- package/src/validation.js +46 -46
- package/src/vec2.js +229 -229
- package/src/vec3.js +1188 -1172
- package/src/vec4.js +347 -347
- package/src/vertex.js +108 -108
- package/src/webgpu/index.js +1 -1
- package/src/webgpu/textureFormats.js +121 -121
package/src/mesh.js
CHANGED
|
@@ -1,424 +1,424 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Small mesh rebuild helpers for shared CarbonEngineJS mesh JSON.
|
|
3
|
-
*
|
|
4
|
-
* These helpers are deliberately framework-free and browser-safe. They accept
|
|
5
|
-
* plain arrays or typed arrays and return plain arrays unless otherwise noted.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
cross,
|
|
10
|
-
length as vec3Length,
|
|
11
|
-
normalize
|
|
12
|
-
} from "gl-matrix/esm/vec3.js";
|
|
13
|
-
|
|
14
|
-
function validatePositions(positions)
|
|
15
|
-
{
|
|
16
|
-
if (!positions || positions.length % 3 !== 0)
|
|
17
|
-
{
|
|
18
|
-
throw new Error("Positions must contain complete xyz vertices");
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function validateIndices(indices, vertexCount)
|
|
23
|
-
{
|
|
24
|
-
if (!indices || indices.length % 3 !== 0)
|
|
25
|
-
{
|
|
26
|
-
throw new Error("Indices must contain complete triangles");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
for (let i = 0; i < indices.length; i++)
|
|
30
|
-
{
|
|
31
|
-
if (!Number.isInteger(indices[i]) || indices[i] < 0 || indices[i] >= vertexCount)
|
|
32
|
-
{
|
|
33
|
-
throw new Error(`Invalid vertex index at ${i}`);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Calculate the unit face normal for a triangle.
|
|
40
|
-
*
|
|
41
|
-
* @param {ArrayLike<number>} a First xyz vertex.
|
|
42
|
-
* @param {ArrayLike<number>} b Second xyz vertex.
|
|
43
|
-
* @param {ArrayLike<number>} c Third xyz vertex.
|
|
44
|
-
* @returns {number[]} Unit triangle normal.
|
|
45
|
-
*/
|
|
46
|
-
export function triangleNormal(a, b, c)
|
|
47
|
-
{
|
|
48
|
-
return triangleNormalTo([ 0, 0, 0 ], a, b, c);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Calculate a unit face normal into caller-owned storage.
|
|
53
|
-
*
|
|
54
|
-
* @param {ArrayLike<number>} out Receiving xyz vector.
|
|
55
|
-
* @param {ArrayLike<number>} a First xyz vertex.
|
|
56
|
-
* @param {ArrayLike<number>} b Second xyz vertex.
|
|
57
|
-
* @param {ArrayLike<number>} c Third xyz vertex.
|
|
58
|
-
* @returns {ArrayLike<number>} The receiving vector.
|
|
59
|
-
*/
|
|
60
|
-
export function triangleNormalTo(out, a, b, c)
|
|
61
|
-
{
|
|
62
|
-
const
|
|
63
|
-
abX = b[0] - a[0],
|
|
64
|
-
abY = b[1] - a[1],
|
|
65
|
-
abZ = b[2] - a[2],
|
|
66
|
-
acX = c[0] - a[0],
|
|
67
|
-
acY = c[1] - a[1],
|
|
68
|
-
acZ = c[2] - a[2];
|
|
69
|
-
|
|
70
|
-
out[0] = abY * acZ - abZ * acY;
|
|
71
|
-
out[1] = abZ * acX - abX * acZ;
|
|
72
|
-
out[2] = abX * acY - abY * acX;
|
|
73
|
-
|
|
74
|
-
const length = Math.hypot(out[0], out[1], out[2]);
|
|
75
|
-
|
|
76
|
-
if (length > 0)
|
|
77
|
-
{
|
|
78
|
-
out[0] /= length;
|
|
79
|
-
out[1] /= length;
|
|
80
|
-
out[2] /= length;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return out;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Twice the area of a triangle.
|
|
88
|
-
*
|
|
89
|
-
* @param {ArrayLike<number>} a First xyz vertex.
|
|
90
|
-
* @param {ArrayLike<number>} b Second xyz vertex.
|
|
91
|
-
* @param {ArrayLike<number>} c Third xyz vertex.
|
|
92
|
-
* @returns {number} Twice the triangle area.
|
|
93
|
-
*/
|
|
94
|
-
export function triangleArea2(a, b, c)
|
|
95
|
-
{
|
|
96
|
-
const normal = [ 0, 0, 0 ];
|
|
97
|
-
cross(
|
|
98
|
-
normal,
|
|
99
|
-
[ b[0] - a[0], b[1] - a[1], b[2] - a[2] ],
|
|
100
|
-
[ c[0] - a[0], c[1] - a[1], c[2] - a[2] ]
|
|
101
|
-
);
|
|
102
|
-
return vec3Length(normal);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Whether a triangle is degenerate.
|
|
107
|
-
*
|
|
108
|
-
* @param {ArrayLike<number>} a First xyz vertex.
|
|
109
|
-
* @param {ArrayLike<number>} b Second xyz vertex.
|
|
110
|
-
* @param {ArrayLike<number>} c Third xyz vertex.
|
|
111
|
-
* @param {number} [epsilon] Area epsilon.
|
|
112
|
-
* @returns {boolean} True when the triangle has no useful area.
|
|
113
|
-
*/
|
|
114
|
-
export function isDegenerateTriangle(a, b, c, epsilon = 1e-12)
|
|
115
|
-
{
|
|
116
|
-
return triangleArea2(a, b, c) <= epsilon;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Compute axis-aligned bounds from flat xyz positions.
|
|
121
|
-
*
|
|
122
|
-
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
123
|
-
* @returns {{minBounds: number[], maxBounds: number[]}} Bounds.
|
|
124
|
-
*/
|
|
125
|
-
export function computeBoundsFromPositions(positions)
|
|
126
|
-
{
|
|
127
|
-
validatePositions(positions);
|
|
128
|
-
|
|
129
|
-
if (!positions.length)
|
|
130
|
-
{
|
|
131
|
-
return { minBounds: [ 0, 0, 0 ], maxBounds: [ 0, 0, 0 ] };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
minBounds = [ positions[0], positions[1], positions[2] ],
|
|
136
|
-
maxBounds = [ positions[0], positions[1], positions[2] ];
|
|
137
|
-
|
|
138
|
-
for (let i = 3; i < positions.length; i += 3)
|
|
139
|
-
{
|
|
140
|
-
for (let c = 0; c < 3; c++)
|
|
141
|
-
{
|
|
142
|
-
const value = positions[i + c];
|
|
143
|
-
if (value < minBounds[c]) minBounds[c] = value;
|
|
144
|
-
if (value > maxBounds[c]) maxBounds[c] = value;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
return { minBounds, maxBounds };
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Compute axis-aligned bounds from `{ vertices: [[x,y,z], ...] }` triangles.
|
|
153
|
-
*
|
|
154
|
-
* @param {Array<{vertices: Array<ArrayLike<number>>}>} triangles Triangle records.
|
|
155
|
-
* @returns {{minBounds: number[], maxBounds: number[]}} Bounds.
|
|
156
|
-
*/
|
|
157
|
-
export function computeBoundsFromTriangles(triangles)
|
|
158
|
-
{
|
|
159
|
-
if (!triangles.length)
|
|
160
|
-
{
|
|
161
|
-
return { minBounds: [ 0, 0, 0 ], maxBounds: [ 0, 0, 0 ] };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
minBounds = [ Infinity, Infinity, Infinity ],
|
|
166
|
-
maxBounds = [ -Infinity, -Infinity, -Infinity ];
|
|
167
|
-
|
|
168
|
-
let hasVertex = false;
|
|
169
|
-
for (const triangle of triangles)
|
|
170
|
-
{
|
|
171
|
-
for (const vertex of triangle.vertices)
|
|
172
|
-
{
|
|
173
|
-
hasVertex = true;
|
|
174
|
-
for (let c = 0; c < 3; c++)
|
|
175
|
-
{
|
|
176
|
-
if (vertex[c] < minBounds[c]) minBounds[c] = vertex[c];
|
|
177
|
-
if (vertex[c] > maxBounds[c]) maxBounds[c] = vertex[c];
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (!hasVertex)
|
|
183
|
-
{
|
|
184
|
-
return { minBounds: [ 0, 0, 0 ], maxBounds: [ 0, 0, 0 ] };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
return { minBounds, maxBounds };
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Generate area-weighted vertex normals from positions and triangle indices.
|
|
192
|
-
*
|
|
193
|
-
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
194
|
-
* @param {ArrayLike<number>} indices Flat triangle indices.
|
|
195
|
-
* @returns {Float32Array} Flat xyz normals.
|
|
196
|
-
*/
|
|
197
|
-
export function generateNormals(positions, indices)
|
|
198
|
-
{
|
|
199
|
-
validatePositions(positions);
|
|
200
|
-
validateIndices(indices, positions.length / 3);
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
vertexCount = positions.length / 3,
|
|
204
|
-
normals = new Float32Array(positions.length);
|
|
205
|
-
|
|
206
|
-
for (let t = 0; t < indices.length; t += 3)
|
|
207
|
-
{
|
|
208
|
-
const
|
|
209
|
-
ia = indices[t] * 3,
|
|
210
|
-
ib = indices[t + 1] * 3,
|
|
211
|
-
ic = indices[t + 2] * 3,
|
|
212
|
-
ax = positions[ia],
|
|
213
|
-
ay = positions[ia + 1],
|
|
214
|
-
az = positions[ia + 2],
|
|
215
|
-
faceNormal = [ 0, 0, 0 ];
|
|
216
|
-
|
|
217
|
-
cross(
|
|
218
|
-
faceNormal,
|
|
219
|
-
[ positions[ib] - ax, positions[ib + 1] - ay, positions[ib + 2] - az ],
|
|
220
|
-
[ positions[ic] - ax, positions[ic + 1] - ay, positions[ic + 2] - az ]
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
for (const offset of [ ia, ib, ic ])
|
|
224
|
-
{
|
|
225
|
-
normals[offset] += faceNormal[0];
|
|
226
|
-
normals[offset + 1] += faceNormal[1];
|
|
227
|
-
normals[offset + 2] += faceNormal[2];
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
for (let i = 0; i < vertexCount; i++)
|
|
232
|
-
{
|
|
233
|
-
const
|
|
234
|
-
offset = i * 3,
|
|
235
|
-
length = Math.hypot(normals[offset], normals[offset + 1], normals[offset + 2]) || 1;
|
|
236
|
-
|
|
237
|
-
normals[offset] /= length;
|
|
238
|
-
normals[offset + 1] /= length;
|
|
239
|
-
normals[offset + 2] /= length;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return normals;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
/**
|
|
246
|
-
* Generate per-vertex tangents from positions, normals, UVs and indices.
|
|
247
|
-
*
|
|
248
|
-
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
249
|
-
* @param {ArrayLike<number>} normals Flat xyz normals.
|
|
250
|
-
* @param {ArrayLike<number>} uvs Flat uv coordinates.
|
|
251
|
-
* @param {ArrayLike<number>} indices Flat triangle indices.
|
|
252
|
-
* @returns {Float32Array} Flat xyz tangents.
|
|
253
|
-
*/
|
|
254
|
-
export function generateTangents(positions, normals, uvs, indices)
|
|
255
|
-
{
|
|
256
|
-
validatePositions(positions);
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
vertexCount = positions.length / 3,
|
|
260
|
-
tan1 = new Float32Array(vertexCount * 3),
|
|
261
|
-
tan2 = new Float32Array(vertexCount * 3);
|
|
262
|
-
|
|
263
|
-
if (!normals || normals.length !== positions.length ||
|
|
264
|
-
!uvs || uvs.length !== vertexCount * 2)
|
|
265
|
-
{
|
|
266
|
-
throw new Error("Tangent channels do not match the vertex count");
|
|
267
|
-
}
|
|
268
|
-
validateIndices(indices, vertexCount);
|
|
269
|
-
|
|
270
|
-
for (let t = 0; t < indices.length; t += 3)
|
|
271
|
-
{
|
|
272
|
-
const
|
|
273
|
-
i0 = indices[t],
|
|
274
|
-
i1 = indices[t + 1],
|
|
275
|
-
i2 = indices[t + 2],
|
|
276
|
-
p0 = i0 * 3,
|
|
277
|
-
p1 = i1 * 3,
|
|
278
|
-
p2 = i2 * 3,
|
|
279
|
-
t0 = i0 * 2,
|
|
280
|
-
t1 = i1 * 2,
|
|
281
|
-
t2 = i2 * 2,
|
|
282
|
-
x1 = positions[p1] - positions[p0],
|
|
283
|
-
y1 = positions[p1 + 1] - positions[p0 + 1],
|
|
284
|
-
z1 = positions[p1 + 2] - positions[p0 + 2],
|
|
285
|
-
x2 = positions[p2] - positions[p0],
|
|
286
|
-
y2 = positions[p2 + 1] - positions[p0 + 1],
|
|
287
|
-
z2 = positions[p2 + 2] - positions[p0 + 2],
|
|
288
|
-
s1 = uvs[t1] - uvs[t0],
|
|
289
|
-
v1 = uvs[t1 + 1] - uvs[t0 + 1],
|
|
290
|
-
s2 = uvs[t2] - uvs[t0],
|
|
291
|
-
v2 = uvs[t2 + 1] - uvs[t0 + 1],
|
|
292
|
-
divisor = s1 * v2 - s2 * v1,
|
|
293
|
-
scale = divisor ? 1 / divisor : 0,
|
|
294
|
-
sx = (v2 * x1 - v1 * x2) * scale,
|
|
295
|
-
sy = (v2 * y1 - v1 * y2) * scale,
|
|
296
|
-
sz = (v2 * z1 - v1 * z2) * scale,
|
|
297
|
-
tx = (s1 * x2 - s2 * x1) * scale,
|
|
298
|
-
ty = (s1 * y2 - s2 * y1) * scale,
|
|
299
|
-
tz = (s1 * z2 - s2 * z1) * scale;
|
|
300
|
-
|
|
301
|
-
for (const offset of [ p0, p1, p2 ])
|
|
302
|
-
{
|
|
303
|
-
tan1[offset] += sx;
|
|
304
|
-
tan1[offset + 1] += sy;
|
|
305
|
-
tan1[offset + 2] += sz;
|
|
306
|
-
tan2[offset] += tx;
|
|
307
|
-
tan2[offset + 1] += ty;
|
|
308
|
-
tan2[offset + 2] += tz;
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const
|
|
313
|
-
tangents = new Float32Array(vertexCount * 3),
|
|
314
|
-
handedness = new Float32Array(vertexCount);
|
|
315
|
-
|
|
316
|
-
for (let i = 0; i < vertexCount; i++)
|
|
317
|
-
{
|
|
318
|
-
const
|
|
319
|
-
offset = i * 3,
|
|
320
|
-
nx = normals[offset],
|
|
321
|
-
ny = normals[offset + 1],
|
|
322
|
-
nz = normals[offset + 2],
|
|
323
|
-
tx = tan1[offset],
|
|
324
|
-
ty = tan1[offset + 1],
|
|
325
|
-
tz = tan1[offset + 2],
|
|
326
|
-
normalDotTangent = nx * tx + ny * ty + nz * tz;
|
|
327
|
-
|
|
328
|
-
let ox = tx - nx * normalDotTangent,
|
|
329
|
-
oy = ty - ny * normalDotTangent,
|
|
330
|
-
oz = tz - nz * normalDotTangent;
|
|
331
|
-
|
|
332
|
-
const length = Math.hypot(ox, oy, oz) || 1;
|
|
333
|
-
|
|
334
|
-
ox /= length;
|
|
335
|
-
oy /= length;
|
|
336
|
-
oz /= length;
|
|
337
|
-
|
|
338
|
-
tangents[offset] = ox;
|
|
339
|
-
tangents[offset + 1] = oy;
|
|
340
|
-
tangents[offset + 2] = oz;
|
|
341
|
-
handedness[i] = (
|
|
342
|
-
(ny * oz - nz * oy) * tan2[offset] +
|
|
343
|
-
(nz * ox - nx * oz) * tan2[offset + 1] +
|
|
344
|
-
(nx * oy - ny * ox) * tan2[offset + 2]
|
|
345
|
-
) < 0 ? -1 : 1;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
Object.defineProperty(tangents, "handedness", { value: handedness });
|
|
349
|
-
return tangents;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* Generate a complete tangent frame while preserving per-vertex UV handedness.
|
|
354
|
-
*
|
|
355
|
-
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
356
|
-
* @param {ArrayLike<number>} normals Flat xyz normals.
|
|
357
|
-
* @param {ArrayLike<number>} uvs Flat uv coordinates.
|
|
358
|
-
* @param {ArrayLike<number>} indices Flat triangle indices.
|
|
359
|
-
* @param {object} [options] Generation options.
|
|
360
|
-
* @returns {{tangents: Float32Array, binormals: number[], handedness: Float32Array}}
|
|
361
|
-
*/
|
|
362
|
-
export function generateTangentFrames(positions, normals, uvs, indices, options)
|
|
363
|
-
{
|
|
364
|
-
const
|
|
365
|
-
tangents = generateTangents(positions, normals, uvs, indices),
|
|
366
|
-
handedness = tangents.handedness,
|
|
367
|
-
binormals = generateBiNormals(normals, tangents, { ...options, handedness });
|
|
368
|
-
|
|
369
|
-
return { tangents, binormals, handedness };
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* Generate binormals as normalized `normal x tangent`.
|
|
374
|
-
*
|
|
375
|
-
* @param {ArrayLike<number>} normals Flat xyz normals.
|
|
376
|
-
* @param {ArrayLike<number>} tangents Flat xyz tangents.
|
|
377
|
-
* @param {object} [options] Generation options.
|
|
378
|
-
* @param {"right"|"left"} [options.uvHandedness] Handedness of generated basis.
|
|
379
|
-
* @returns {number[]} Flat xyz binormals.
|
|
380
|
-
*/
|
|
381
|
-
export function generateBiNormals(normals, tangents, options = {})
|
|
382
|
-
{
|
|
383
|
-
if (normals.length !== tangents.length || normals.length % 3 !== 0)
|
|
384
|
-
{
|
|
385
|
-
throw new Error("generateBiNormals requires matching complete xyz channels");
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const
|
|
389
|
-
conventionSign = options.uvHandedness === "left" ? -1 : 1,
|
|
390
|
-
binormals = new Array(normals.length);
|
|
391
|
-
|
|
392
|
-
for (let i = 0; i < normals.length; i += 3)
|
|
393
|
-
{
|
|
394
|
-
const
|
|
395
|
-
vertexSign = options.handedness?.[i / 3] ?? tangents.handedness?.[i / 3] ?? 1,
|
|
396
|
-
sign = conventionSign * vertexSign;
|
|
397
|
-
const b = normalize(
|
|
398
|
-
[ 0, 0, 0 ],
|
|
399
|
-
[
|
|
400
|
-
normals[i + 1] * tangents[i + 2] - normals[i + 2] * tangents[i + 1],
|
|
401
|
-
normals[i + 2] * tangents[i] - normals[i] * tangents[i + 2],
|
|
402
|
-
normals[i] * tangents[i + 1] - normals[i + 1] * tangents[i]
|
|
403
|
-
]
|
|
404
|
-
);
|
|
405
|
-
binormals[i] = b[0] * sign;
|
|
406
|
-
binormals[i + 1] = b[1] * sign;
|
|
407
|
-
binormals[i + 2] = b[2] * sign;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
return binormals;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
export const mesh = Object.freeze({
|
|
414
|
-
triangleNormal,
|
|
415
|
-
triangleNormalTo,
|
|
416
|
-
triangleArea2,
|
|
417
|
-
isDegenerateTriangle,
|
|
418
|
-
computeBoundsFromPositions,
|
|
419
|
-
computeBoundsFromTriangles,
|
|
420
|
-
generateNormals,
|
|
421
|
-
generateTangents,
|
|
422
|
-
generateTangentFrames,
|
|
423
|
-
generateBiNormals
|
|
424
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Small mesh rebuild helpers for shared CarbonEngineJS mesh JSON.
|
|
3
|
+
*
|
|
4
|
+
* These helpers are deliberately framework-free and browser-safe. They accept
|
|
5
|
+
* plain arrays or typed arrays and return plain arrays unless otherwise noted.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
cross,
|
|
10
|
+
length as vec3Length,
|
|
11
|
+
normalize
|
|
12
|
+
} from "gl-matrix/esm/vec3.js";
|
|
13
|
+
|
|
14
|
+
function validatePositions(positions)
|
|
15
|
+
{
|
|
16
|
+
if (!positions || positions.length % 3 !== 0)
|
|
17
|
+
{
|
|
18
|
+
throw new Error("Positions must contain complete xyz vertices");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function validateIndices(indices, vertexCount)
|
|
23
|
+
{
|
|
24
|
+
if (!indices || indices.length % 3 !== 0)
|
|
25
|
+
{
|
|
26
|
+
throw new Error("Indices must contain complete triangles");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
for (let i = 0; i < indices.length; i++)
|
|
30
|
+
{
|
|
31
|
+
if (!Number.isInteger(indices[i]) || indices[i] < 0 || indices[i] >= vertexCount)
|
|
32
|
+
{
|
|
33
|
+
throw new Error(`Invalid vertex index at ${i}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Calculate the unit face normal for a triangle.
|
|
40
|
+
*
|
|
41
|
+
* @param {ArrayLike<number>} a First xyz vertex.
|
|
42
|
+
* @param {ArrayLike<number>} b Second xyz vertex.
|
|
43
|
+
* @param {ArrayLike<number>} c Third xyz vertex.
|
|
44
|
+
* @returns {number[]} Unit triangle normal.
|
|
45
|
+
*/
|
|
46
|
+
export function triangleNormal(a, b, c)
|
|
47
|
+
{
|
|
48
|
+
return triangleNormalTo([ 0, 0, 0 ], a, b, c);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Calculate a unit face normal into caller-owned storage.
|
|
53
|
+
*
|
|
54
|
+
* @param {ArrayLike<number>} out Receiving xyz vector.
|
|
55
|
+
* @param {ArrayLike<number>} a First xyz vertex.
|
|
56
|
+
* @param {ArrayLike<number>} b Second xyz vertex.
|
|
57
|
+
* @param {ArrayLike<number>} c Third xyz vertex.
|
|
58
|
+
* @returns {ArrayLike<number>} The receiving vector.
|
|
59
|
+
*/
|
|
60
|
+
export function triangleNormalTo(out, a, b, c)
|
|
61
|
+
{
|
|
62
|
+
const
|
|
63
|
+
abX = b[0] - a[0],
|
|
64
|
+
abY = b[1] - a[1],
|
|
65
|
+
abZ = b[2] - a[2],
|
|
66
|
+
acX = c[0] - a[0],
|
|
67
|
+
acY = c[1] - a[1],
|
|
68
|
+
acZ = c[2] - a[2];
|
|
69
|
+
|
|
70
|
+
out[0] = abY * acZ - abZ * acY;
|
|
71
|
+
out[1] = abZ * acX - abX * acZ;
|
|
72
|
+
out[2] = abX * acY - abY * acX;
|
|
73
|
+
|
|
74
|
+
const length = Math.hypot(out[0], out[1], out[2]);
|
|
75
|
+
|
|
76
|
+
if (length > 0)
|
|
77
|
+
{
|
|
78
|
+
out[0] /= length;
|
|
79
|
+
out[1] /= length;
|
|
80
|
+
out[2] /= length;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Twice the area of a triangle.
|
|
88
|
+
*
|
|
89
|
+
* @param {ArrayLike<number>} a First xyz vertex.
|
|
90
|
+
* @param {ArrayLike<number>} b Second xyz vertex.
|
|
91
|
+
* @param {ArrayLike<number>} c Third xyz vertex.
|
|
92
|
+
* @returns {number} Twice the triangle area.
|
|
93
|
+
*/
|
|
94
|
+
export function triangleArea2(a, b, c)
|
|
95
|
+
{
|
|
96
|
+
const normal = [ 0, 0, 0 ];
|
|
97
|
+
cross(
|
|
98
|
+
normal,
|
|
99
|
+
[ b[0] - a[0], b[1] - a[1], b[2] - a[2] ],
|
|
100
|
+
[ c[0] - a[0], c[1] - a[1], c[2] - a[2] ]
|
|
101
|
+
);
|
|
102
|
+
return vec3Length(normal);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Whether a triangle is degenerate.
|
|
107
|
+
*
|
|
108
|
+
* @param {ArrayLike<number>} a First xyz vertex.
|
|
109
|
+
* @param {ArrayLike<number>} b Second xyz vertex.
|
|
110
|
+
* @param {ArrayLike<number>} c Third xyz vertex.
|
|
111
|
+
* @param {number} [epsilon] Area epsilon.
|
|
112
|
+
* @returns {boolean} True when the triangle has no useful area.
|
|
113
|
+
*/
|
|
114
|
+
export function isDegenerateTriangle(a, b, c, epsilon = 1e-12)
|
|
115
|
+
{
|
|
116
|
+
return triangleArea2(a, b, c) <= epsilon;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Compute axis-aligned bounds from flat xyz positions.
|
|
121
|
+
*
|
|
122
|
+
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
123
|
+
* @returns {{minBounds: number[], maxBounds: number[]}} Bounds.
|
|
124
|
+
*/
|
|
125
|
+
export function computeBoundsFromPositions(positions)
|
|
126
|
+
{
|
|
127
|
+
validatePositions(positions);
|
|
128
|
+
|
|
129
|
+
if (!positions.length)
|
|
130
|
+
{
|
|
131
|
+
return { minBounds: [ 0, 0, 0 ], maxBounds: [ 0, 0, 0 ] };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const
|
|
135
|
+
minBounds = [ positions[0], positions[1], positions[2] ],
|
|
136
|
+
maxBounds = [ positions[0], positions[1], positions[2] ];
|
|
137
|
+
|
|
138
|
+
for (let i = 3; i < positions.length; i += 3)
|
|
139
|
+
{
|
|
140
|
+
for (let c = 0; c < 3; c++)
|
|
141
|
+
{
|
|
142
|
+
const value = positions[i + c];
|
|
143
|
+
if (value < minBounds[c]) minBounds[c] = value;
|
|
144
|
+
if (value > maxBounds[c]) maxBounds[c] = value;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { minBounds, maxBounds };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Compute axis-aligned bounds from `{ vertices: [[x,y,z], ...] }` triangles.
|
|
153
|
+
*
|
|
154
|
+
* @param {Array<{vertices: Array<ArrayLike<number>>}>} triangles Triangle records.
|
|
155
|
+
* @returns {{minBounds: number[], maxBounds: number[]}} Bounds.
|
|
156
|
+
*/
|
|
157
|
+
export function computeBoundsFromTriangles(triangles)
|
|
158
|
+
{
|
|
159
|
+
if (!triangles.length)
|
|
160
|
+
{
|
|
161
|
+
return { minBounds: [ 0, 0, 0 ], maxBounds: [ 0, 0, 0 ] };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const
|
|
165
|
+
minBounds = [ Infinity, Infinity, Infinity ],
|
|
166
|
+
maxBounds = [ -Infinity, -Infinity, -Infinity ];
|
|
167
|
+
|
|
168
|
+
let hasVertex = false;
|
|
169
|
+
for (const triangle of triangles)
|
|
170
|
+
{
|
|
171
|
+
for (const vertex of triangle.vertices)
|
|
172
|
+
{
|
|
173
|
+
hasVertex = true;
|
|
174
|
+
for (let c = 0; c < 3; c++)
|
|
175
|
+
{
|
|
176
|
+
if (vertex[c] < minBounds[c]) minBounds[c] = vertex[c];
|
|
177
|
+
if (vertex[c] > maxBounds[c]) maxBounds[c] = vertex[c];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (!hasVertex)
|
|
183
|
+
{
|
|
184
|
+
return { minBounds: [ 0, 0, 0 ], maxBounds: [ 0, 0, 0 ] };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return { minBounds, maxBounds };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Generate area-weighted vertex normals from positions and triangle indices.
|
|
192
|
+
*
|
|
193
|
+
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
194
|
+
* @param {ArrayLike<number>} indices Flat triangle indices.
|
|
195
|
+
* @returns {Float32Array} Flat xyz normals.
|
|
196
|
+
*/
|
|
197
|
+
export function generateNormals(positions, indices)
|
|
198
|
+
{
|
|
199
|
+
validatePositions(positions);
|
|
200
|
+
validateIndices(indices, positions.length / 3);
|
|
201
|
+
|
|
202
|
+
const
|
|
203
|
+
vertexCount = positions.length / 3,
|
|
204
|
+
normals = new Float32Array(positions.length);
|
|
205
|
+
|
|
206
|
+
for (let t = 0; t < indices.length; t += 3)
|
|
207
|
+
{
|
|
208
|
+
const
|
|
209
|
+
ia = indices[t] * 3,
|
|
210
|
+
ib = indices[t + 1] * 3,
|
|
211
|
+
ic = indices[t + 2] * 3,
|
|
212
|
+
ax = positions[ia],
|
|
213
|
+
ay = positions[ia + 1],
|
|
214
|
+
az = positions[ia + 2],
|
|
215
|
+
faceNormal = [ 0, 0, 0 ];
|
|
216
|
+
|
|
217
|
+
cross(
|
|
218
|
+
faceNormal,
|
|
219
|
+
[ positions[ib] - ax, positions[ib + 1] - ay, positions[ib + 2] - az ],
|
|
220
|
+
[ positions[ic] - ax, positions[ic + 1] - ay, positions[ic + 2] - az ]
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
for (const offset of [ ia, ib, ic ])
|
|
224
|
+
{
|
|
225
|
+
normals[offset] += faceNormal[0];
|
|
226
|
+
normals[offset + 1] += faceNormal[1];
|
|
227
|
+
normals[offset + 2] += faceNormal[2];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
for (let i = 0; i < vertexCount; i++)
|
|
232
|
+
{
|
|
233
|
+
const
|
|
234
|
+
offset = i * 3,
|
|
235
|
+
length = Math.hypot(normals[offset], normals[offset + 1], normals[offset + 2]) || 1;
|
|
236
|
+
|
|
237
|
+
normals[offset] /= length;
|
|
238
|
+
normals[offset + 1] /= length;
|
|
239
|
+
normals[offset + 2] /= length;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return normals;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Generate per-vertex tangents from positions, normals, UVs and indices.
|
|
247
|
+
*
|
|
248
|
+
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
249
|
+
* @param {ArrayLike<number>} normals Flat xyz normals.
|
|
250
|
+
* @param {ArrayLike<number>} uvs Flat uv coordinates.
|
|
251
|
+
* @param {ArrayLike<number>} indices Flat triangle indices.
|
|
252
|
+
* @returns {Float32Array} Flat xyz tangents.
|
|
253
|
+
*/
|
|
254
|
+
export function generateTangents(positions, normals, uvs, indices)
|
|
255
|
+
{
|
|
256
|
+
validatePositions(positions);
|
|
257
|
+
|
|
258
|
+
const
|
|
259
|
+
vertexCount = positions.length / 3,
|
|
260
|
+
tan1 = new Float32Array(vertexCount * 3),
|
|
261
|
+
tan2 = new Float32Array(vertexCount * 3);
|
|
262
|
+
|
|
263
|
+
if (!normals || normals.length !== positions.length ||
|
|
264
|
+
!uvs || uvs.length !== vertexCount * 2)
|
|
265
|
+
{
|
|
266
|
+
throw new Error("Tangent channels do not match the vertex count");
|
|
267
|
+
}
|
|
268
|
+
validateIndices(indices, vertexCount);
|
|
269
|
+
|
|
270
|
+
for (let t = 0; t < indices.length; t += 3)
|
|
271
|
+
{
|
|
272
|
+
const
|
|
273
|
+
i0 = indices[t],
|
|
274
|
+
i1 = indices[t + 1],
|
|
275
|
+
i2 = indices[t + 2],
|
|
276
|
+
p0 = i0 * 3,
|
|
277
|
+
p1 = i1 * 3,
|
|
278
|
+
p2 = i2 * 3,
|
|
279
|
+
t0 = i0 * 2,
|
|
280
|
+
t1 = i1 * 2,
|
|
281
|
+
t2 = i2 * 2,
|
|
282
|
+
x1 = positions[p1] - positions[p0],
|
|
283
|
+
y1 = positions[p1 + 1] - positions[p0 + 1],
|
|
284
|
+
z1 = positions[p1 + 2] - positions[p0 + 2],
|
|
285
|
+
x2 = positions[p2] - positions[p0],
|
|
286
|
+
y2 = positions[p2 + 1] - positions[p0 + 1],
|
|
287
|
+
z2 = positions[p2 + 2] - positions[p0 + 2],
|
|
288
|
+
s1 = uvs[t1] - uvs[t0],
|
|
289
|
+
v1 = uvs[t1 + 1] - uvs[t0 + 1],
|
|
290
|
+
s2 = uvs[t2] - uvs[t0],
|
|
291
|
+
v2 = uvs[t2 + 1] - uvs[t0 + 1],
|
|
292
|
+
divisor = s1 * v2 - s2 * v1,
|
|
293
|
+
scale = divisor ? 1 / divisor : 0,
|
|
294
|
+
sx = (v2 * x1 - v1 * x2) * scale,
|
|
295
|
+
sy = (v2 * y1 - v1 * y2) * scale,
|
|
296
|
+
sz = (v2 * z1 - v1 * z2) * scale,
|
|
297
|
+
tx = (s1 * x2 - s2 * x1) * scale,
|
|
298
|
+
ty = (s1 * y2 - s2 * y1) * scale,
|
|
299
|
+
tz = (s1 * z2 - s2 * z1) * scale;
|
|
300
|
+
|
|
301
|
+
for (const offset of [ p0, p1, p2 ])
|
|
302
|
+
{
|
|
303
|
+
tan1[offset] += sx;
|
|
304
|
+
tan1[offset + 1] += sy;
|
|
305
|
+
tan1[offset + 2] += sz;
|
|
306
|
+
tan2[offset] += tx;
|
|
307
|
+
tan2[offset + 1] += ty;
|
|
308
|
+
tan2[offset + 2] += tz;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const
|
|
313
|
+
tangents = new Float32Array(vertexCount * 3),
|
|
314
|
+
handedness = new Float32Array(vertexCount);
|
|
315
|
+
|
|
316
|
+
for (let i = 0; i < vertexCount; i++)
|
|
317
|
+
{
|
|
318
|
+
const
|
|
319
|
+
offset = i * 3,
|
|
320
|
+
nx = normals[offset],
|
|
321
|
+
ny = normals[offset + 1],
|
|
322
|
+
nz = normals[offset + 2],
|
|
323
|
+
tx = tan1[offset],
|
|
324
|
+
ty = tan1[offset + 1],
|
|
325
|
+
tz = tan1[offset + 2],
|
|
326
|
+
normalDotTangent = nx * tx + ny * ty + nz * tz;
|
|
327
|
+
|
|
328
|
+
let ox = tx - nx * normalDotTangent,
|
|
329
|
+
oy = ty - ny * normalDotTangent,
|
|
330
|
+
oz = tz - nz * normalDotTangent;
|
|
331
|
+
|
|
332
|
+
const length = Math.hypot(ox, oy, oz) || 1;
|
|
333
|
+
|
|
334
|
+
ox /= length;
|
|
335
|
+
oy /= length;
|
|
336
|
+
oz /= length;
|
|
337
|
+
|
|
338
|
+
tangents[offset] = ox;
|
|
339
|
+
tangents[offset + 1] = oy;
|
|
340
|
+
tangents[offset + 2] = oz;
|
|
341
|
+
handedness[i] = (
|
|
342
|
+
(ny * oz - nz * oy) * tan2[offset] +
|
|
343
|
+
(nz * ox - nx * oz) * tan2[offset + 1] +
|
|
344
|
+
(nx * oy - ny * ox) * tan2[offset + 2]
|
|
345
|
+
) < 0 ? -1 : 1;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
Object.defineProperty(tangents, "handedness", { value: handedness });
|
|
349
|
+
return tangents;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Generate a complete tangent frame while preserving per-vertex UV handedness.
|
|
354
|
+
*
|
|
355
|
+
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
356
|
+
* @param {ArrayLike<number>} normals Flat xyz normals.
|
|
357
|
+
* @param {ArrayLike<number>} uvs Flat uv coordinates.
|
|
358
|
+
* @param {ArrayLike<number>} indices Flat triangle indices.
|
|
359
|
+
* @param {object} [options] Generation options.
|
|
360
|
+
* @returns {{tangents: Float32Array, binormals: number[], handedness: Float32Array}}
|
|
361
|
+
*/
|
|
362
|
+
export function generateTangentFrames(positions, normals, uvs, indices, options)
|
|
363
|
+
{
|
|
364
|
+
const
|
|
365
|
+
tangents = generateTangents(positions, normals, uvs, indices),
|
|
366
|
+
handedness = tangents.handedness,
|
|
367
|
+
binormals = generateBiNormals(normals, tangents, { ...options, handedness });
|
|
368
|
+
|
|
369
|
+
return { tangents, binormals, handedness };
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Generate binormals as normalized `normal x tangent`.
|
|
374
|
+
*
|
|
375
|
+
* @param {ArrayLike<number>} normals Flat xyz normals.
|
|
376
|
+
* @param {ArrayLike<number>} tangents Flat xyz tangents.
|
|
377
|
+
* @param {object} [options] Generation options.
|
|
378
|
+
* @param {"right"|"left"} [options.uvHandedness] Handedness of generated basis.
|
|
379
|
+
* @returns {number[]} Flat xyz binormals.
|
|
380
|
+
*/
|
|
381
|
+
export function generateBiNormals(normals, tangents, options = {})
|
|
382
|
+
{
|
|
383
|
+
if (normals.length !== tangents.length || normals.length % 3 !== 0)
|
|
384
|
+
{
|
|
385
|
+
throw new Error("generateBiNormals requires matching complete xyz channels");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const
|
|
389
|
+
conventionSign = options.uvHandedness === "left" ? -1 : 1,
|
|
390
|
+
binormals = new Array(normals.length);
|
|
391
|
+
|
|
392
|
+
for (let i = 0; i < normals.length; i += 3)
|
|
393
|
+
{
|
|
394
|
+
const
|
|
395
|
+
vertexSign = options.handedness?.[i / 3] ?? tangents.handedness?.[i / 3] ?? 1,
|
|
396
|
+
sign = conventionSign * vertexSign;
|
|
397
|
+
const b = normalize(
|
|
398
|
+
[ 0, 0, 0 ],
|
|
399
|
+
[
|
|
400
|
+
normals[i + 1] * tangents[i + 2] - normals[i + 2] * tangents[i + 1],
|
|
401
|
+
normals[i + 2] * tangents[i] - normals[i] * tangents[i + 2],
|
|
402
|
+
normals[i] * tangents[i + 1] - normals[i + 1] * tangents[i]
|
|
403
|
+
]
|
|
404
|
+
);
|
|
405
|
+
binormals[i] = b[0] * sign;
|
|
406
|
+
binormals[i + 1] = b[1] * sign;
|
|
407
|
+
binormals[i + 2] = b[2] * sign;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return binormals;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export const mesh = Object.freeze({
|
|
414
|
+
triangleNormal,
|
|
415
|
+
triangleNormalTo,
|
|
416
|
+
triangleArea2,
|
|
417
|
+
isDegenerateTriangle,
|
|
418
|
+
computeBoundsFromPositions,
|
|
419
|
+
computeBoundsFromTriangles,
|
|
420
|
+
generateNormals,
|
|
421
|
+
generateTangents,
|
|
422
|
+
generateTangentFrames,
|
|
423
|
+
generateBiNormals
|
|
424
|
+
});
|