@carbonenginejs/runtime-utils 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.
- package/LICENSE +21 -0
- package/NOTICE +23 -0
- package/README.md +56 -0
- package/THIRD-PARTY-NOTICES.md +38 -0
- package/docs/README.md +81 -0
- package/docs/architecture.md +101 -0
- package/docs/concepts/foundation-consolidation.md +86 -0
- package/docs/const-kb.md +92 -0
- package/docs/core-types/DECORATOR-TODOS.md +25 -0
- package/docs/core-types/README.md +203 -0
- package/docs/reference/api.md +70 -0
- package/docs/reference/classes/README.md +115 -0
- package/package.json +132 -0
- package/src/arrays.js +5 -0
- package/src/audio/audioFormats.js +34 -0
- package/src/audio/index.js +1 -0
- package/src/box3.js +1385 -0
- package/src/bytes.js +56 -0
- package/src/compression.js +56 -0
- package/src/constants/index.js +6 -0
- package/src/constants.js +15 -0
- package/src/curve.js +419 -0
- package/src/d3d/dxgiFormats.js +46 -0
- package/src/d3d/index.js +2 -0
- package/src/d3d/primitiveTopology.js +11 -0
- package/src/document/CjsCarbonDocument.js +212 -0
- package/src/document/CjsClassRegistry.js +373 -0
- package/src/document/CjsDocumentDehydrator.js +142 -0
- package/src/document/CjsDocumentHydrator.js +156 -0
- package/src/document/CjsStructRegistry.js +348 -0
- package/src/document/hydrationAdapter.js +129 -0
- package/src/document/index.js +6 -0
- package/src/geometry/box.js +137 -0
- package/src/geometry/cylinder.js +244 -0
- package/src/geometry/helpers/LICENSE +15 -0
- package/src/geometry/helpers/earcut.js +766 -0
- package/src/geometry/helpers/misc.js +103 -0
- package/src/geometry/index.js +8 -0
- package/src/geometry/json.js +165 -0
- package/src/geometry/lathe.js +172 -0
- package/src/geometry/octahedron.js +0 -0
- package/src/geometry/plane.js +81 -0
- package/src/geometry/shape.js +95 -0
- package/src/geometry/sphere.js +123 -0
- package/src/geometry/torus.js +96 -0
- package/src/graphics/colorSpaces.js +22 -0
- package/src/graphics/index.js +4 -0
- package/src/graphics/pixelFormats.js +158 -0
- package/src/graphics/textureDimensions.js +22 -0
- package/src/graphics/trinityEnums.js +87 -0
- package/src/index.js +58 -0
- package/src/is.js +524 -0
- package/src/json.js +23 -0
- package/src/lifecycle/CjsLifecycleState.js +77 -0
- package/src/lifecycle/index.js +1 -0
- package/src/lne3.js +497 -0
- package/src/lookup.js +48 -0
- package/src/mat3.js +131 -0
- package/src/mat4.js +699 -0
- package/src/math/index.js +25 -0
- package/src/math/scalar.js +63 -0
- package/src/media/index.js +1 -0
- package/src/media/mediaTypes.js +50 -0
- package/src/mesh.js +394 -0
- package/src/model/CjsEventEmitter.js +333 -0
- package/src/model/CjsModel.js +1544 -0
- package/src/model/CjsModelState.js +72 -0
- package/src/model/index.js +4 -0
- package/src/model/sourceRecordUtils.js +54 -0
- package/src/noise.js +310 -0
- package/src/num.js +827 -0
- package/src/path.js +21 -0
- package/src/pln.js +762 -0
- package/src/pool.js +160 -0
- package/src/quat.js +144 -0
- package/src/ray3.js +1085 -0
- package/src/renderContext/formats.js +145 -0
- package/src/renderContext/index.js +5 -0
- package/src/renderContext/presentation.js +125 -0
- package/src/renderContext/resources.js +27 -0
- package/src/renderContext/upscaling.js +22 -0
- package/src/renderContext/window.js +20 -0
- package/src/runtime/CjsRuntimeState.js +50 -0
- package/src/schema/CjsSchema.js +1009 -0
- package/src/schema/index.js +17 -0
- package/src/shader/index.js +1 -0
- package/src/shader/shaderStages.js +37 -0
- package/src/sph3.js +754 -0
- package/src/tangent.js +288 -0
- package/src/text.js +40 -0
- package/src/tri3.js +650 -0
- package/src/types/carbonTypes.js +635 -0
- package/src/types/index.js +2 -0
- package/src/utils.js +58 -0
- package/src/validation.js +46 -0
- package/src/vec2.js +229 -0
- package/src/vec3.js +1172 -0
- package/src/vec4.js +347 -0
- package/src/vertex.js +108 -0
- package/src/webgpu/index.js +1 -0
- package/src/webgpu/textureFormats.js +121 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export {
|
|
2
|
+
EPSILON as defaultEpsilon,
|
|
3
|
+
TWO_PI as tau,
|
|
4
|
+
clamp,
|
|
5
|
+
cubicHermite,
|
|
6
|
+
cubicHermiteDerivative,
|
|
7
|
+
smoothStep,
|
|
8
|
+
smootherStep
|
|
9
|
+
} from "../num.js";
|
|
10
|
+
|
|
11
|
+
/** Restricts a value to the inclusive range from zero to one. */
|
|
12
|
+
export function saturate(value)
|
|
13
|
+
{
|
|
14
|
+
return Math.max(0, Math.min(1, value));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Linearly interpolates without clamping the interpolation amount. */
|
|
18
|
+
export function lerp(start, end, amount)
|
|
19
|
+
{
|
|
20
|
+
return start + (end - start) * amount;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Compares two numbers with an explicit relative tolerance. */
|
|
24
|
+
export function approximatelyEqual(a, b, epsilon = 1e-6)
|
|
25
|
+
{
|
|
26
|
+
if (epsilon < 0)
|
|
27
|
+
{
|
|
28
|
+
throw new RangeError("epsilon must be non-negative.");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Math.abs(a - b) <= epsilon * Math.max(1, Math.abs(a), Math.abs(b));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Converts degrees to radians. */
|
|
35
|
+
export function degreesToRadians(degrees)
|
|
36
|
+
{
|
|
37
|
+
return degrees * Math.PI / 180;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Converts radians to degrees. */
|
|
41
|
+
export function radiansToDegrees(radians)
|
|
42
|
+
{
|
|
43
|
+
return radians * 180 / Math.PI;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Wraps degrees into the inclusive range from -180 to 180. */
|
|
47
|
+
export function wrapDegrees(degrees)
|
|
48
|
+
{
|
|
49
|
+
let wrapped = degrees % 360;
|
|
50
|
+
if (wrapped > 180) wrapped -= 360;
|
|
51
|
+
if (wrapped < -180) wrapped += 360;
|
|
52
|
+
return wrapped;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Wraps radians into the inclusive range from -pi to pi. */
|
|
56
|
+
export function wrapRadians(radians)
|
|
57
|
+
{
|
|
58
|
+
const tau = Math.PI * 2;
|
|
59
|
+
let wrapped = radians % tau;
|
|
60
|
+
if (wrapped > Math.PI) wrapped -= tau;
|
|
61
|
+
if (wrapped < -Math.PI) wrapped += tau;
|
|
62
|
+
return wrapped;
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./mediaTypes.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical high-level media families used by format metadata and resources.
|
|
3
|
+
*/
|
|
4
|
+
export const MediaType = Object.freeze({
|
|
5
|
+
AUDIO: "audio",
|
|
6
|
+
DATA: "data",
|
|
7
|
+
GEOMETRY: "geometry",
|
|
8
|
+
IMAGE: "image",
|
|
9
|
+
SCHEMA: "schema",
|
|
10
|
+
SHADER: "shader",
|
|
11
|
+
TEXTURE: "texture",
|
|
12
|
+
VIDEO: "video"
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Canonical semantic payload roles emitted by format readers.
|
|
17
|
+
*/
|
|
18
|
+
export const PayloadType = Object.freeze({
|
|
19
|
+
AUDIO: "audio",
|
|
20
|
+
DATA: "data",
|
|
21
|
+
GEOMETRY: "geometry",
|
|
22
|
+
IMAGE: "image",
|
|
23
|
+
RAW: "raw",
|
|
24
|
+
SCHEMA: "schema",
|
|
25
|
+
SHADER: "shader",
|
|
26
|
+
TEXTURE: "texture",
|
|
27
|
+
VIDEO: "video"
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Normalize a media type token to lowercase canonical text.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} value Input media type.
|
|
34
|
+
* @returns {string} Normalized media type.
|
|
35
|
+
*/
|
|
36
|
+
export function normalizeMediaType(value)
|
|
37
|
+
{
|
|
38
|
+
return value ? String(value).trim().toLowerCase() : "";
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Normalize a payload type token to lowercase canonical text.
|
|
43
|
+
*
|
|
44
|
+
* @param {string} value Input payload type.
|
|
45
|
+
* @returns {string} Normalized payload type.
|
|
46
|
+
*/
|
|
47
|
+
export function normalizePayloadType(value)
|
|
48
|
+
{
|
|
49
|
+
return value ? String(value).trim().toLowerCase() : "";
|
|
50
|
+
}
|
package/src/mesh.js
ADDED
|
@@ -0,0 +1,394 @@
|
|
|
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
|
+
const normal = [ 0, 0, 0 ];
|
|
49
|
+
cross(
|
|
50
|
+
normal,
|
|
51
|
+
[ b[0] - a[0], b[1] - a[1], b[2] - a[2] ],
|
|
52
|
+
[ c[0] - a[0], c[1] - a[1], c[2] - a[2] ]
|
|
53
|
+
);
|
|
54
|
+
return normalize(normal, normal);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Twice the area of a triangle.
|
|
59
|
+
*
|
|
60
|
+
* @param {ArrayLike<number>} a First xyz vertex.
|
|
61
|
+
* @param {ArrayLike<number>} b Second xyz vertex.
|
|
62
|
+
* @param {ArrayLike<number>} c Third xyz vertex.
|
|
63
|
+
* @returns {number} Twice the triangle area.
|
|
64
|
+
*/
|
|
65
|
+
export function triangleArea2(a, b, c)
|
|
66
|
+
{
|
|
67
|
+
const normal = [ 0, 0, 0 ];
|
|
68
|
+
cross(
|
|
69
|
+
normal,
|
|
70
|
+
[ b[0] - a[0], b[1] - a[1], b[2] - a[2] ],
|
|
71
|
+
[ c[0] - a[0], c[1] - a[1], c[2] - a[2] ]
|
|
72
|
+
);
|
|
73
|
+
return vec3Length(normal);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Whether a triangle is degenerate.
|
|
78
|
+
*
|
|
79
|
+
* @param {ArrayLike<number>} a First xyz vertex.
|
|
80
|
+
* @param {ArrayLike<number>} b Second xyz vertex.
|
|
81
|
+
* @param {ArrayLike<number>} c Third xyz vertex.
|
|
82
|
+
* @param {number} [epsilon] Area epsilon.
|
|
83
|
+
* @returns {boolean} True when the triangle has no useful area.
|
|
84
|
+
*/
|
|
85
|
+
export function isDegenerateTriangle(a, b, c, epsilon = 1e-12)
|
|
86
|
+
{
|
|
87
|
+
return triangleArea2(a, b, c) <= epsilon;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Compute axis-aligned bounds from flat xyz positions.
|
|
92
|
+
*
|
|
93
|
+
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
94
|
+
* @returns {{minBounds: number[], maxBounds: number[]}} Bounds.
|
|
95
|
+
*/
|
|
96
|
+
export function computeBoundsFromPositions(positions)
|
|
97
|
+
{
|
|
98
|
+
validatePositions(positions);
|
|
99
|
+
|
|
100
|
+
if (!positions.length)
|
|
101
|
+
{
|
|
102
|
+
return { minBounds: [ 0, 0, 0 ], maxBounds: [ 0, 0, 0 ] };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const
|
|
106
|
+
minBounds = [ positions[0], positions[1], positions[2] ],
|
|
107
|
+
maxBounds = [ positions[0], positions[1], positions[2] ];
|
|
108
|
+
|
|
109
|
+
for (let i = 3; i < positions.length; i += 3)
|
|
110
|
+
{
|
|
111
|
+
for (let c = 0; c < 3; c++)
|
|
112
|
+
{
|
|
113
|
+
const value = positions[i + c];
|
|
114
|
+
if (value < minBounds[c]) minBounds[c] = value;
|
|
115
|
+
if (value > maxBounds[c]) maxBounds[c] = value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { minBounds, maxBounds };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Compute axis-aligned bounds from `{ vertices: [[x,y,z], ...] }` triangles.
|
|
124
|
+
*
|
|
125
|
+
* @param {Array<{vertices: Array<ArrayLike<number>>}>} triangles Triangle records.
|
|
126
|
+
* @returns {{minBounds: number[], maxBounds: number[]}} Bounds.
|
|
127
|
+
*/
|
|
128
|
+
export function computeBoundsFromTriangles(triangles)
|
|
129
|
+
{
|
|
130
|
+
if (!triangles.length)
|
|
131
|
+
{
|
|
132
|
+
return { minBounds: [ 0, 0, 0 ], maxBounds: [ 0, 0, 0 ] };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const
|
|
136
|
+
minBounds = [ Infinity, Infinity, Infinity ],
|
|
137
|
+
maxBounds = [ -Infinity, -Infinity, -Infinity ];
|
|
138
|
+
|
|
139
|
+
let hasVertex = false;
|
|
140
|
+
for (const triangle of triangles)
|
|
141
|
+
{
|
|
142
|
+
for (const vertex of triangle.vertices)
|
|
143
|
+
{
|
|
144
|
+
hasVertex = true;
|
|
145
|
+
for (let c = 0; c < 3; c++)
|
|
146
|
+
{
|
|
147
|
+
if (vertex[c] < minBounds[c]) minBounds[c] = vertex[c];
|
|
148
|
+
if (vertex[c] > maxBounds[c]) maxBounds[c] = vertex[c];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (!hasVertex)
|
|
154
|
+
{
|
|
155
|
+
return { minBounds: [ 0, 0, 0 ], maxBounds: [ 0, 0, 0 ] };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { minBounds, maxBounds };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Generate area-weighted vertex normals from positions and triangle indices.
|
|
163
|
+
*
|
|
164
|
+
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
165
|
+
* @param {ArrayLike<number>} indices Flat triangle indices.
|
|
166
|
+
* @returns {Float32Array} Flat xyz normals.
|
|
167
|
+
*/
|
|
168
|
+
export function generateNormals(positions, indices)
|
|
169
|
+
{
|
|
170
|
+
validatePositions(positions);
|
|
171
|
+
validateIndices(indices, positions.length / 3);
|
|
172
|
+
|
|
173
|
+
const
|
|
174
|
+
vertexCount = positions.length / 3,
|
|
175
|
+
normals = new Float32Array(positions.length);
|
|
176
|
+
|
|
177
|
+
for (let t = 0; t < indices.length; t += 3)
|
|
178
|
+
{
|
|
179
|
+
const
|
|
180
|
+
ia = indices[t] * 3,
|
|
181
|
+
ib = indices[t + 1] * 3,
|
|
182
|
+
ic = indices[t + 2] * 3,
|
|
183
|
+
ax = positions[ia],
|
|
184
|
+
ay = positions[ia + 1],
|
|
185
|
+
az = positions[ia + 2],
|
|
186
|
+
faceNormal = [ 0, 0, 0 ];
|
|
187
|
+
|
|
188
|
+
cross(
|
|
189
|
+
faceNormal,
|
|
190
|
+
[ positions[ib] - ax, positions[ib + 1] - ay, positions[ib + 2] - az ],
|
|
191
|
+
[ positions[ic] - ax, positions[ic + 1] - ay, positions[ic + 2] - az ]
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
for (const offset of [ ia, ib, ic ])
|
|
195
|
+
{
|
|
196
|
+
normals[offset] += faceNormal[0];
|
|
197
|
+
normals[offset + 1] += faceNormal[1];
|
|
198
|
+
normals[offset + 2] += faceNormal[2];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
for (let i = 0; i < vertexCount; i++)
|
|
203
|
+
{
|
|
204
|
+
const
|
|
205
|
+
offset = i * 3,
|
|
206
|
+
length = Math.hypot(normals[offset], normals[offset + 1], normals[offset + 2]) || 1;
|
|
207
|
+
|
|
208
|
+
normals[offset] /= length;
|
|
209
|
+
normals[offset + 1] /= length;
|
|
210
|
+
normals[offset + 2] /= length;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return normals;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Generate per-vertex tangents from positions, normals, UVs and indices.
|
|
218
|
+
*
|
|
219
|
+
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
220
|
+
* @param {ArrayLike<number>} normals Flat xyz normals.
|
|
221
|
+
* @param {ArrayLike<number>} uvs Flat uv coordinates.
|
|
222
|
+
* @param {ArrayLike<number>} indices Flat triangle indices.
|
|
223
|
+
* @returns {Float32Array} Flat xyz tangents.
|
|
224
|
+
*/
|
|
225
|
+
export function generateTangents(positions, normals, uvs, indices)
|
|
226
|
+
{
|
|
227
|
+
validatePositions(positions);
|
|
228
|
+
|
|
229
|
+
const
|
|
230
|
+
vertexCount = positions.length / 3,
|
|
231
|
+
tan1 = new Float32Array(vertexCount * 3),
|
|
232
|
+
tan2 = new Float32Array(vertexCount * 3);
|
|
233
|
+
|
|
234
|
+
if (!normals || normals.length !== positions.length ||
|
|
235
|
+
!uvs || uvs.length !== vertexCount * 2)
|
|
236
|
+
{
|
|
237
|
+
throw new Error("Tangent channels do not match the vertex count");
|
|
238
|
+
}
|
|
239
|
+
validateIndices(indices, vertexCount);
|
|
240
|
+
|
|
241
|
+
for (let t = 0; t < indices.length; t += 3)
|
|
242
|
+
{
|
|
243
|
+
const
|
|
244
|
+
i0 = indices[t],
|
|
245
|
+
i1 = indices[t + 1],
|
|
246
|
+
i2 = indices[t + 2],
|
|
247
|
+
p0 = i0 * 3,
|
|
248
|
+
p1 = i1 * 3,
|
|
249
|
+
p2 = i2 * 3,
|
|
250
|
+
t0 = i0 * 2,
|
|
251
|
+
t1 = i1 * 2,
|
|
252
|
+
t2 = i2 * 2,
|
|
253
|
+
x1 = positions[p1] - positions[p0],
|
|
254
|
+
y1 = positions[p1 + 1] - positions[p0 + 1],
|
|
255
|
+
z1 = positions[p1 + 2] - positions[p0 + 2],
|
|
256
|
+
x2 = positions[p2] - positions[p0],
|
|
257
|
+
y2 = positions[p2 + 1] - positions[p0 + 1],
|
|
258
|
+
z2 = positions[p2 + 2] - positions[p0 + 2],
|
|
259
|
+
s1 = uvs[t1] - uvs[t0],
|
|
260
|
+
v1 = uvs[t1 + 1] - uvs[t0 + 1],
|
|
261
|
+
s2 = uvs[t2] - uvs[t0],
|
|
262
|
+
v2 = uvs[t2 + 1] - uvs[t0 + 1],
|
|
263
|
+
divisor = s1 * v2 - s2 * v1,
|
|
264
|
+
scale = divisor ? 1 / divisor : 0,
|
|
265
|
+
sx = (v2 * x1 - v1 * x2) * scale,
|
|
266
|
+
sy = (v2 * y1 - v1 * y2) * scale,
|
|
267
|
+
sz = (v2 * z1 - v1 * z2) * scale,
|
|
268
|
+
tx = (s1 * x2 - s2 * x1) * scale,
|
|
269
|
+
ty = (s1 * y2 - s2 * y1) * scale,
|
|
270
|
+
tz = (s1 * z2 - s2 * z1) * scale;
|
|
271
|
+
|
|
272
|
+
for (const offset of [ p0, p1, p2 ])
|
|
273
|
+
{
|
|
274
|
+
tan1[offset] += sx;
|
|
275
|
+
tan1[offset + 1] += sy;
|
|
276
|
+
tan1[offset + 2] += sz;
|
|
277
|
+
tan2[offset] += tx;
|
|
278
|
+
tan2[offset + 1] += ty;
|
|
279
|
+
tan2[offset + 2] += tz;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const
|
|
284
|
+
tangents = new Float32Array(vertexCount * 3),
|
|
285
|
+
handedness = new Float32Array(vertexCount);
|
|
286
|
+
|
|
287
|
+
for (let i = 0; i < vertexCount; i++)
|
|
288
|
+
{
|
|
289
|
+
const
|
|
290
|
+
offset = i * 3,
|
|
291
|
+
nx = normals[offset],
|
|
292
|
+
ny = normals[offset + 1],
|
|
293
|
+
nz = normals[offset + 2],
|
|
294
|
+
tx = tan1[offset],
|
|
295
|
+
ty = tan1[offset + 1],
|
|
296
|
+
tz = tan1[offset + 2],
|
|
297
|
+
normalDotTangent = nx * tx + ny * ty + nz * tz;
|
|
298
|
+
|
|
299
|
+
let ox = tx - nx * normalDotTangent,
|
|
300
|
+
oy = ty - ny * normalDotTangent,
|
|
301
|
+
oz = tz - nz * normalDotTangent;
|
|
302
|
+
|
|
303
|
+
const length = Math.hypot(ox, oy, oz) || 1;
|
|
304
|
+
|
|
305
|
+
ox /= length;
|
|
306
|
+
oy /= length;
|
|
307
|
+
oz /= length;
|
|
308
|
+
|
|
309
|
+
tangents[offset] = ox;
|
|
310
|
+
tangents[offset + 1] = oy;
|
|
311
|
+
tangents[offset + 2] = oz;
|
|
312
|
+
handedness[i] = (
|
|
313
|
+
(ny * oz - nz * oy) * tan2[offset] +
|
|
314
|
+
(nz * ox - nx * oz) * tan2[offset + 1] +
|
|
315
|
+
(nx * oy - ny * ox) * tan2[offset + 2]
|
|
316
|
+
) < 0 ? -1 : 1;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
Object.defineProperty(tangents, "handedness", { value: handedness });
|
|
320
|
+
return tangents;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Generate a complete tangent frame while preserving per-vertex UV handedness.
|
|
325
|
+
*
|
|
326
|
+
* @param {ArrayLike<number>} positions Flat xyz positions.
|
|
327
|
+
* @param {ArrayLike<number>} normals Flat xyz normals.
|
|
328
|
+
* @param {ArrayLike<number>} uvs Flat uv coordinates.
|
|
329
|
+
* @param {ArrayLike<number>} indices Flat triangle indices.
|
|
330
|
+
* @param {object} [options] Generation options.
|
|
331
|
+
* @returns {{tangents: Float32Array, binormals: number[], handedness: Float32Array}}
|
|
332
|
+
*/
|
|
333
|
+
export function generateTangentFrames(positions, normals, uvs, indices, options)
|
|
334
|
+
{
|
|
335
|
+
const
|
|
336
|
+
tangents = generateTangents(positions, normals, uvs, indices),
|
|
337
|
+
handedness = tangents.handedness,
|
|
338
|
+
binormals = generateBiNormals(normals, tangents, { ...options, handedness });
|
|
339
|
+
|
|
340
|
+
return { tangents, binormals, handedness };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Generate binormals as normalized `normal x tangent`.
|
|
345
|
+
*
|
|
346
|
+
* @param {ArrayLike<number>} normals Flat xyz normals.
|
|
347
|
+
* @param {ArrayLike<number>} tangents Flat xyz tangents.
|
|
348
|
+
* @param {object} [options] Generation options.
|
|
349
|
+
* @param {"right"|"left"} [options.uvHandedness] Handedness of generated basis.
|
|
350
|
+
* @returns {number[]} Flat xyz binormals.
|
|
351
|
+
*/
|
|
352
|
+
export function generateBiNormals(normals, tangents, options = {})
|
|
353
|
+
{
|
|
354
|
+
if (normals.length !== tangents.length || normals.length % 3 !== 0)
|
|
355
|
+
{
|
|
356
|
+
throw new Error("generateBiNormals requires matching complete xyz channels");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const
|
|
360
|
+
conventionSign = options.uvHandedness === "left" ? -1 : 1,
|
|
361
|
+
binormals = new Array(normals.length);
|
|
362
|
+
|
|
363
|
+
for (let i = 0; i < normals.length; i += 3)
|
|
364
|
+
{
|
|
365
|
+
const
|
|
366
|
+
vertexSign = options.handedness?.[i / 3] ?? tangents.handedness?.[i / 3] ?? 1,
|
|
367
|
+
sign = conventionSign * vertexSign;
|
|
368
|
+
const b = normalize(
|
|
369
|
+
[ 0, 0, 0 ],
|
|
370
|
+
[
|
|
371
|
+
normals[i + 1] * tangents[i + 2] - normals[i + 2] * tangents[i + 1],
|
|
372
|
+
normals[i + 2] * tangents[i] - normals[i] * tangents[i + 2],
|
|
373
|
+
normals[i] * tangents[i + 1] - normals[i + 1] * tangents[i]
|
|
374
|
+
]
|
|
375
|
+
);
|
|
376
|
+
binormals[i] = b[0] * sign;
|
|
377
|
+
binormals[i + 1] = b[1] * sign;
|
|
378
|
+
binormals[i + 2] = b[2] * sign;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return binormals;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
export const mesh = Object.freeze({
|
|
385
|
+
triangleNormal,
|
|
386
|
+
triangleArea2,
|
|
387
|
+
isDegenerateTriangle,
|
|
388
|
+
computeBoundsFromPositions,
|
|
389
|
+
computeBoundsFromTriangles,
|
|
390
|
+
generateNormals,
|
|
391
|
+
generateTangents,
|
|
392
|
+
generateTangentFrames,
|
|
393
|
+
generateBiNormals
|
|
394
|
+
});
|