@forgeax/engine-geometry 0.1.23 → 0.1.24
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/dist/__tests__/physical-tangent.unit.test.d.ts +2 -0
- package/dist/__tests__/physical-tangent.unit.test.d.ts.map +1 -0
- package/dist/index.mjs +42 -25
- package/dist/index.mjs.map +1 -1
- package/dist/tangent.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/physical-tangent.unit.test.ts +34 -0
- package/src/tangent.ts +46 -32
package/dist/tangent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tangent.d.ts","sourceRoot":"","sources":["../src/tangent.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tangent.d.ts","sourceRoot":"","sources":["../src/tangent.ts"],"names":[],"mappings":"AA6BA,OAAO,EAAqB,UAAU,EAAW,KAAK,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAmF5F;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,YAAY,EACrB,GAAG,EAAE,YAAY,EACjB,OAAO,CAAC,EAAE,WAAW,GAAG,WAAW,GAClC,MAAM,CAAC,YAAY,EAAE,UAAU,CAAC,CAyLlC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forgeax/engine-geometry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -22,10 +22,10 @@
|
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@forgeax/engine-ecs": "0.1.
|
|
26
|
-
"@forgeax/engine-math": "0.1.
|
|
27
|
-
"@forgeax/engine-pack": "0.1.
|
|
28
|
-
"@forgeax/engine-types": "0.1.
|
|
25
|
+
"@forgeax/engine-ecs": "0.1.24",
|
|
26
|
+
"@forgeax/engine-math": "0.1.24",
|
|
27
|
+
"@forgeax/engine-pack": "0.1.24",
|
|
28
|
+
"@forgeax/engine-types": "0.1.24"
|
|
29
29
|
},
|
|
30
30
|
"forgeax": {
|
|
31
31
|
"metrics": {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { computeTangentVec4 } from '@forgeax/engine-geometry';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
const positions = new Float32Array([0, 0, 0, 1, 0, 0, 0, 1, 0]);
|
|
5
|
+
const normals = new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1]);
|
|
6
|
+
|
|
7
|
+
describe('physical material tangent admission', () => {
|
|
8
|
+
it('preserves vec4 handedness for a valid rigid tangent', () => {
|
|
9
|
+
const result = computeTangentVec4(
|
|
10
|
+
positions,
|
|
11
|
+
normals,
|
|
12
|
+
new Float32Array([0, 0, 1, 0, 0, 1]),
|
|
13
|
+
new Uint32Array([0, 1, 2]),
|
|
14
|
+
);
|
|
15
|
+
expect(result.ok).toBe(true);
|
|
16
|
+
if (result.ok) expect([...result.value]).toEqual([1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1]);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('fails closed for a degenerate UV triangle instead of inventing a tangent', () => {
|
|
20
|
+
const result = computeTangentVec4(
|
|
21
|
+
positions,
|
|
22
|
+
normals,
|
|
23
|
+
new Float32Array([0, 0, 0, 0, 0, 0]),
|
|
24
|
+
new Uint32Array([0, 1, 2]),
|
|
25
|
+
);
|
|
26
|
+
expect(result.ok).toBe(false);
|
|
27
|
+
if (!result.ok) {
|
|
28
|
+
expect(result.error.code).toBe('asset-parse-failed');
|
|
29
|
+
expect(result.error.detail).toMatchObject({
|
|
30
|
+
reason: expect.stringContaining('material-tangent-required'),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
});
|
package/src/tangent.ts
CHANGED
|
@@ -22,8 +22,10 @@
|
|
|
22
22
|
// or side effects. Indices may be Uint16Array, Uint32Array, or
|
|
23
23
|
// undefined (sequential 0..vertexCount-1 -- triangle list). Degenerate
|
|
24
24
|
// triangles (zero area UV; det ~ 0) are skipped from the accumulation;
|
|
25
|
-
//
|
|
26
|
-
//
|
|
25
|
+
// Inputs with no valid UV-derived triangle fail closed. Procedural pole/seam
|
|
26
|
+
// vertices may have no local contribution while sharing valid neighboring
|
|
27
|
+
// faces; they retain the established deterministic frame until the material
|
|
28
|
+
// admission layer has mesh-level tangent context.
|
|
27
29
|
|
|
28
30
|
import { ASSET_ERROR_HINTS, AssetError, err, ok, type Result } from '@forgeax/engine-types';
|
|
29
31
|
|
|
@@ -154,6 +156,7 @@ export function computeTangentVec4(
|
|
|
154
156
|
// the running sum is the per-vertex .w handedness.
|
|
155
157
|
const accumT = new Float32Array(vertexCount * 3);
|
|
156
158
|
const accumSign = new Float32Array(vertexCount);
|
|
159
|
+
let validTriangleCount = 0;
|
|
157
160
|
|
|
158
161
|
const triangleCount = indices !== undefined ? indices.length / 3 : vertexCount / 3;
|
|
159
162
|
|
|
@@ -207,6 +210,7 @@ export function computeTangentVec4(
|
|
|
207
210
|
const cz = dP1x * dP2y - dP1y * dP2x;
|
|
208
211
|
const faceArea = 0.5 * Math.sqrt(cx * cx + cy * cy + cz * cz);
|
|
209
212
|
if (faceArea < EPSILON) continue;
|
|
213
|
+
validTriangleCount += 1;
|
|
210
214
|
|
|
211
215
|
const signDet = det >= 0 ? 1 : -1;
|
|
212
216
|
const signedWeight = faceArea * signDet;
|
|
@@ -219,6 +223,16 @@ export function computeTangentVec4(
|
|
|
219
223
|
}
|
|
220
224
|
}
|
|
221
225
|
|
|
226
|
+
if (validTriangleCount === 0) {
|
|
227
|
+
return err(
|
|
228
|
+
tangentInputError(
|
|
229
|
+
'tangent',
|
|
230
|
+
0,
|
|
231
|
+
'material-tangent-required: no triangle has a valid UV-derived tangent',
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
222
236
|
const out = new Float32Array(vertexCount * 4);
|
|
223
237
|
for (let v = 0; v < vertexCount; v++) {
|
|
224
238
|
let tx = accumT[v * 3] ?? 0;
|
|
@@ -230,36 +244,35 @@ export function computeTangentVec4(
|
|
|
230
244
|
|
|
231
245
|
const tLen = Math.sqrt(tx * tx + ty * ty + tz * tz);
|
|
232
246
|
if (tLen < EPSILON) {
|
|
233
|
-
// Fallback: pick any direction perpendicular to N. Use the axis
|
|
234
|
-
// with the smallest |normal component| to maximise numerical
|
|
235
|
-
// stability of the cross product.
|
|
236
247
|
const ax = Math.abs(nx);
|
|
237
248
|
const ay = Math.abs(ny);
|
|
238
249
|
const az = Math.abs(nz);
|
|
239
250
|
let rx = 1;
|
|
240
251
|
let ry = 0;
|
|
241
252
|
let rz = 0;
|
|
242
|
-
if (ax
|
|
243
|
-
rx = 1;
|
|
244
|
-
ry = 0;
|
|
245
|
-
rz = 0;
|
|
246
|
-
} else if (ay <= ax && ay <= az) {
|
|
253
|
+
if (ax > ay && ax > az) {
|
|
247
254
|
rx = 0;
|
|
248
255
|
ry = 1;
|
|
249
|
-
|
|
250
|
-
} else {
|
|
256
|
+
} else if (ay > ax && ay > az) {
|
|
251
257
|
rx = 0;
|
|
252
|
-
ry = 0;
|
|
253
258
|
rz = 1;
|
|
254
259
|
}
|
|
255
|
-
// T = normalize(cross(N, R))
|
|
256
260
|
tx = ny * rz - nz * ry;
|
|
257
261
|
ty = nz * rx - nx * rz;
|
|
258
262
|
tz = nx * ry - ny * rx;
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
+
const fallbackLength = Math.sqrt(tx * tx + ty * ty + tz * tz);
|
|
264
|
+
if (fallbackLength < EPSILON) {
|
|
265
|
+
return err(
|
|
266
|
+
tangentInputError(
|
|
267
|
+
'tangent',
|
|
268
|
+
v,
|
|
269
|
+
'material-tangent-required: supplied normal cannot define a tangent frame',
|
|
270
|
+
),
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
tx /= fallbackLength;
|
|
274
|
+
ty /= fallbackLength;
|
|
275
|
+
tz /= fallbackLength;
|
|
263
276
|
} else {
|
|
264
277
|
tx /= tLen;
|
|
265
278
|
ty /= tLen;
|
|
@@ -273,34 +286,35 @@ export function computeTangentVec4(
|
|
|
273
286
|
let gz = tz - dotTN * nz;
|
|
274
287
|
const gLen = Math.sqrt(gx * gx + gy * gy + gz * gz);
|
|
275
288
|
if (gLen < EPSILON) {
|
|
276
|
-
// tangent collinear with normal after accumulation; reuse fallback
|
|
277
|
-
// basis. Pick perpendicular via the smallest-axis trick again.
|
|
278
289
|
const ax = Math.abs(nx);
|
|
279
290
|
const ay = Math.abs(ny);
|
|
280
291
|
const az = Math.abs(nz);
|
|
281
292
|
let rx = 1;
|
|
282
293
|
let ry = 0;
|
|
283
294
|
let rz = 0;
|
|
284
|
-
if (ax
|
|
285
|
-
rx = 1;
|
|
286
|
-
ry = 0;
|
|
287
|
-
rz = 0;
|
|
288
|
-
} else if (ay <= ax && ay <= az) {
|
|
295
|
+
if (ax > ay && ax > az) {
|
|
289
296
|
rx = 0;
|
|
290
297
|
ry = 1;
|
|
291
|
-
|
|
292
|
-
} else {
|
|
298
|
+
} else if (ay > ax && ay > az) {
|
|
293
299
|
rx = 0;
|
|
294
|
-
ry = 0;
|
|
295
300
|
rz = 1;
|
|
296
301
|
}
|
|
297
302
|
gx = ny * rz - nz * ry;
|
|
298
303
|
gy = nz * rx - nx * rz;
|
|
299
304
|
gz = nx * ry - ny * rx;
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
305
|
+
const fallbackLength = Math.sqrt(gx * gx + gy * gy + gz * gz);
|
|
306
|
+
if (fallbackLength < EPSILON) {
|
|
307
|
+
return err(
|
|
308
|
+
tangentInputError(
|
|
309
|
+
'tangent',
|
|
310
|
+
v,
|
|
311
|
+
'material-tangent-required: supplied normal cannot define a tangent frame',
|
|
312
|
+
),
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
gx /= fallbackLength;
|
|
316
|
+
gy /= fallbackLength;
|
|
317
|
+
gz /= fallbackLength;
|
|
304
318
|
} else {
|
|
305
319
|
gx /= gLen;
|
|
306
320
|
gy /= gLen;
|