Skip to content

Commit 6bcbb5c

Browse files
authored
chore(functions): Add tests for unwrap() and unwrapPrimitives() (#1662)
1 parent f9b69af commit 6bcbb5c

File tree

1 file changed

+278
-0
lines changed

1 file changed

+278
-0
lines changed
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import test from 'ava';
2+
import { Accessor, Document, Primitive, vec2 } from '@gltf-transform/core';
3+
import {
4+
getPrimitiveVertexCount,
5+
unweld,
6+
unwrap,
7+
unwrapPrimitives,
8+
VertexCountMethod,
9+
} from '@gltf-transform/functions';
10+
import { createTorusKnotPrimitive, logger } from '@gltf-transform/test-utils';
11+
import * as watlas from 'watlas';
12+
13+
await watlas.Initialize();
14+
15+
test('unwrapPrimitives - unindexed', async (t) => {
16+
const document = new Document().setLogger(logger);
17+
const prim = createTorusKnotPrimitive(document, { tubularSegments: 6 });
18+
document.createMesh().addPrimitive(prim);
19+
20+
await document.transform(unweld());
21+
22+
t.falsy(prim.getIndices(), 'indices = null (initial)');
23+
24+
unwrapPrimitives([prim], { watlas, overwrite: false });
25+
26+
t.falsy(prim.getIndices(), 'indices = null (overwrite=false)');
27+
28+
unwrapPrimitives([prim], { watlas, overwrite: true });
29+
30+
t.truthy(prim.getIndices(), 'indices != null (overwrite=true)');
31+
});
32+
33+
test('unwrapPrimitives - vertex count', async (t) => {
34+
const document = new Document().setLogger(logger);
35+
const prim = createTorusKnotPrimitive(document, { tubularSegments: 6 });
36+
37+
const srcIndexCount = prim.getIndices().getCount();
38+
const srcVertexCount = getPrimitiveVertexCount(prim, VertexCountMethod.UPLOAD);
39+
40+
unwrapPrimitives([prim], { watlas, overwrite: true });
41+
42+
const dstIndexCount = prim.getIndices().getCount();
43+
const dstVertexCount = getPrimitiveVertexCount(prim, VertexCountMethod.UPLOAD);
44+
45+
t.is(dstIndexCount, srcIndexCount, 'srcIndexCount == dst.indexCount');
46+
t.is(srcVertexCount, 63, 'srcVertexCount = 63');
47+
t.true(dstVertexCount > 100 && dstVertexCount < 150, '100 < dstVertexCount < 150');
48+
t.is(getPrimitiveVertexCount(prim, VertexCountMethod.UNUSED), 0, 'no unused vertices');
49+
});
50+
51+
test('unwrapPrimitives - texcoord', async (t) => {
52+
const document = new Document().setLogger(logger);
53+
const prim = createTorusKnotPrimitive(document, { tubularSegments: 6 });
54+
55+
t.truthy(prim.getAttribute('TEXCOORD_0'), 'TEXCOORD_0 = A (initial)');
56+
t.is(prim.getAttribute('TEXCOORD_1'), null, 'TEXCOORD_1 = null (initial)');
57+
t.is(prim.getAttribute('TEXCOORD_3'), null, 'TEXCOORD_0 = null (initial)');
58+
59+
unwrapPrimitives([prim], { watlas, texcoord: 1 });
60+
61+
t.truthy(prim.getAttribute('TEXCOORD_0'), 'TEXCOORD_0 = A');
62+
t.truthy(prim.getAttribute('TEXCOORD_1'), 'TEXCOORD_1 = B');
63+
t.is(prim.getAttribute('TEXCOORD_3'), null, 'TEXCOORD_0 = null');
64+
t.notDeepEqual(prim.getAttribute('TEXCOORD_0'), prim.getAttribute('TEXCOORD_1'), 'A != B');
65+
66+
unwrapPrimitives([prim], { watlas, texcoord: 2 });
67+
68+
t.truthy(prim.getAttribute('TEXCOORD_0'), 'TEXCOORD_0 = A');
69+
t.truthy(prim.getAttribute('TEXCOORD_1'), 'TEXCOORD_1 = B');
70+
t.truthy(prim.getAttribute('TEXCOORD_2'), 'TEXCOORD_2 = C');
71+
t.notDeepEqual(prim.getAttribute('TEXCOORD_0'), prim.getAttribute('TEXCOORD_1'), 'A != B');
72+
t.notDeepEqual(prim.getAttribute('TEXCOORD_1'), prim.getAttribute('TEXCOORD_2'), 'B != C');
73+
});
74+
75+
test('unwrapPrimitives - overwrite', async (t) => {
76+
const document = new Document().setLogger(logger);
77+
const prim = createTorusKnotPrimitive(document, { tubularSegments: 6 });
78+
79+
const texCoordA = prim.getAttribute('TEXCOORD_0');
80+
const texCoordB = texCoordA.clone();
81+
prim.setAttribute('TEXCOORD_1', texCoordB);
82+
83+
scaleAccessor(texCoordA, 2);
84+
scaleAccessor(texCoordB, 0.5);
85+
86+
const texCoordBoundsA = getTexCoordBounds(texCoordA);
87+
const texCoordBoundsB = getTexCoordBounds(texCoordB);
88+
const texCoordBoundsC = { min: [0, 0] as vec2, max: [1, 1] as vec2 };
89+
90+
t.is(prim.getAttribute('TEXCOORD_0'), texCoordA, 'TEXCOORD_0 = A');
91+
t.is(prim.getAttribute('TEXCOORD_1'), texCoordB, 'TEXCOORD_1 = B');
92+
t.deepEqual(texCoordBoundsA, { min: [0, 0], max: [2, 2] }, 'TEXCOORD_0 bounds');
93+
t.deepEqual(texCoordBoundsB, { min: [0, 0], max: [0.5, 0.5] }, 'TEXCOORD_1 bounds');
94+
95+
unwrapPrimitives([prim], { watlas, texcoord: 1, overwrite: false });
96+
97+
t.is(prim.getAttribute('TEXCOORD_0'), texCoordA, 'TEXCOORD_0 = A');
98+
t.is(prim.getAttribute('TEXCOORD_1'), texCoordB, 'TEXCOORD_1 = B');
99+
t.deepEqual(getTexCoordBounds(prim.getAttribute('TEXCOORD_0')), texCoordBoundsA, 'TEXCOORD_0 = A');
100+
t.deepEqual(getTexCoordBounds(prim.getAttribute('TEXCOORD_1')), texCoordBoundsB, 'TEXCOORD_1 = B');
101+
102+
unwrapPrimitives([prim], { watlas, texcoord: 1, overwrite: true });
103+
104+
// accessors may be replaced, and vertices reordered, but UV layout is the same.
105+
t.deepEqual(getTexCoordBounds(prim.getAttribute('TEXCOORD_0')), texCoordBoundsA, 'TEXCOORD_0 = A');
106+
t.deepEqual(getTexCoordBounds(prim.getAttribute('TEXCOORD_1')), texCoordBoundsC, 'TEXCOORD_1 = C');
107+
});
108+
109+
test('unwrap - primitive', async (t) => {
110+
const document = new Document().setLogger(logger);
111+
112+
const primA = createTorusKnotPrimitive(document, { tubularSegments: 6 });
113+
const primB = createTorusKnotPrimitive(document, { tubularSegments: 7 });
114+
const primC = createTorusKnotPrimitive(document, { tubularSegments: 8 });
115+
const meshA = document.createMesh('A').addPrimitive(primA).addPrimitive(primB);
116+
const meshB = document.createMesh('B').addPrimitive(primC);
117+
const nodeA = document.createNode('A').setMesh(meshA);
118+
const nodeB = document.createNode('B').setMesh(meshB);
119+
document.createScene().addChild(nodeA).addChild(nodeB);
120+
121+
scaleAccessor(primA.getAttribute('POSITION'), 10);
122+
scaleAccessor(primB.getAttribute('POSITION'), 5);
123+
scaleAccessor(primC.getAttribute('POSITION'), 0.5);
124+
125+
primA.setAttribute('TEXCOORD_0', null);
126+
primB.setAttribute('TEXCOORD_0', null);
127+
primC.setAttribute('TEXCOORD_0', null);
128+
129+
await document.transform(unwrap({ watlas, groupBy: 'primitive', overwrite: true }));
130+
131+
const areaA = getTexCoordArea(primA, 0);
132+
const areaB = getTexCoordArea(primB, 0);
133+
const areaC = getTexCoordArea(primC, 0);
134+
135+
t.true(areaA > 0.4 && areaA < 0.6, '0.4 < areaA < 0.6');
136+
t.true(areaB > 0.4 && areaB < 0.6, '0.4 < areaB < 0.6');
137+
t.true(areaC > 0.4 && areaC < 0.6, '0.4 < areaC < 0.6');
138+
});
139+
140+
test('unwrap - mesh', async (t) => {
141+
const document = new Document().setLogger(logger);
142+
143+
const primA = createTorusKnotPrimitive(document, { tubularSegments: 6 });
144+
const primB = createTorusKnotPrimitive(document, { tubularSegments: 7 });
145+
const primC = createTorusKnotPrimitive(document, { tubularSegments: 8 });
146+
const meshA = document.createMesh('A').addPrimitive(primA).addPrimitive(primB);
147+
const meshB = document.createMesh('B').addPrimitive(primC);
148+
const nodeA = document.createNode('A').setMesh(meshA);
149+
const nodeB = document.createNode('B').setMesh(meshB);
150+
document.createScene().addChild(nodeA).addChild(nodeB);
151+
152+
scaleAccessor(primA.getAttribute('POSITION'), 10);
153+
scaleAccessor(primB.getAttribute('POSITION'), 5);
154+
scaleAccessor(primC.getAttribute('POSITION'), 0.5);
155+
156+
primA.setAttribute('TEXCOORD_0', null);
157+
primB.setAttribute('TEXCOORD_0', null);
158+
primC.setAttribute('TEXCOORD_0', null);
159+
160+
await document.transform(unwrap({ watlas, groupBy: 'mesh', overwrite: true }));
161+
162+
const areaA = getTexCoordArea(primA, 0);
163+
const areaB = getTexCoordArea(primB, 0);
164+
const areaC = getTexCoordArea(primC, 0);
165+
166+
t.true(areaA > 0.3 && areaA < 0.5, '0.3 < areaA < 0.5');
167+
t.true(areaB > 0.05 && areaB < 0.2, '0.05 < areaB < 0.2');
168+
t.true(areaC > 0.4 && areaC < 0.6, '0.4 < areaC < 0.6');
169+
});
170+
171+
test('unwrap - scene', async (t) => {
172+
const document = new Document().setLogger(logger);
173+
174+
const primA = createTorusKnotPrimitive(document, { tubularSegments: 6 });
175+
const primB = createTorusKnotPrimitive(document, { tubularSegments: 7 });
176+
const primC = createTorusKnotPrimitive(document, { tubularSegments: 8 });
177+
const meshA = document.createMesh('A').addPrimitive(primA).addPrimitive(primB);
178+
const meshB = document.createMesh('B').addPrimitive(primC);
179+
const nodeA = document.createNode('A').setMesh(meshA);
180+
const nodeB = document.createNode('B').setMesh(meshB);
181+
document.createScene().addChild(nodeA).addChild(nodeB);
182+
183+
scaleAccessor(primA.getAttribute('POSITION'), 10);
184+
scaleAccessor(primB.getAttribute('POSITION'), 5);
185+
scaleAccessor(primC.getAttribute('POSITION'), 0.5);
186+
187+
primA.setAttribute('TEXCOORD_0', null);
188+
primB.setAttribute('TEXCOORD_0', null);
189+
primC.setAttribute('TEXCOORD_0', null);
190+
191+
await document.transform(unwrap({ watlas, groupBy: 'scene', overwrite: true }));
192+
193+
const areaA = getTexCoordArea(primA, 0);
194+
const areaB = getTexCoordArea(primB, 0);
195+
const areaC = getTexCoordArea(primC, 0);
196+
197+
t.true(areaA > 0.3 && areaA < 0.5, '0.3 < areaA < 0.5');
198+
t.true(areaB > 0.05 && areaB < 0.2, '0.05 < areaB < 0.2');
199+
t.true(areaC > 0.0 && areaC < 0.01, '0.0 < areaC < 0.01');
200+
});
201+
202+
test('unwrap - scene scaled', async (t) => {
203+
const document = new Document().setLogger(logger);
204+
205+
const primA = createTorusKnotPrimitive(document, { tubularSegments: 6 });
206+
const primB = createTorusKnotPrimitive(document, { tubularSegments: 7 });
207+
const primC = createTorusKnotPrimitive(document, { tubularSegments: 8 });
208+
const meshA = document.createMesh('A').addPrimitive(primA).addPrimitive(primB);
209+
const meshB = document.createMesh('B').addPrimitive(primC);
210+
const nodeA = document.createNode('A').setMesh(meshA);
211+
const nodeB = document.createNode('B').setMesh(meshB);
212+
document.createScene().addChild(nodeA).addChild(nodeB);
213+
214+
nodeA.setScale([10, 10, 10]);
215+
nodeB.setScale([0.5, 0.5, 0.5]);
216+
217+
primA.setAttribute('TEXCOORD_0', null);
218+
primB.setAttribute('TEXCOORD_0', null);
219+
primC.setAttribute('TEXCOORD_0', null);
220+
221+
await document.transform(unwrap({ watlas, groupBy: 'scene', overwrite: true }));
222+
223+
const areaA = getTexCoordArea(primA, 0);
224+
const areaB = getTexCoordArea(primB, 0);
225+
const areaC = getTexCoordArea(primC, 0);
226+
227+
t.true(areaA > 0.2 && areaA < 0.3, '0.2 < areaA < 0.3');
228+
t.true(areaB > 0.2 && areaB < 0.3, '0.2 < areaB < 0.3');
229+
t.true(areaC > 0.0 && areaC < 0.01, '0.0 < areaC < 0.01');
230+
});
231+
232+
/* UTILITIES */
233+
234+
function scaleAccessor(attribute: Accessor, scale: number): void {
235+
for (let i = 0, el = [], il = attribute.getCount(); i < il; i++) {
236+
attribute.getElement(i, el);
237+
for (let j = 0; j < el.length; j++) el[j] *= scale;
238+
attribute.setElement(i, el);
239+
}
240+
}
241+
242+
function getTexCoordBounds(attribute: Accessor): { min: vec2; max: vec2 } {
243+
const bounds = { min: [Infinity, Infinity] as vec2, max: [-Infinity, -Infinity] as vec2 };
244+
245+
const el: vec2 = [0, 0];
246+
for (let i = 0, il = attribute.getCount(); i < il; i++) {
247+
attribute.getElement(i, el);
248+
bounds.min[0] = Math.min(bounds.min[0], el[0]);
249+
bounds.min[1] = Math.min(bounds.min[1], el[1]);
250+
bounds.max[0] = Math.max(bounds.max[0], el[0]);
251+
bounds.max[1] = Math.max(bounds.max[1], el[1]);
252+
}
253+
254+
return {
255+
min: bounds.min.map((v) => Math.round(v * 1000) / 1000) as vec2,
256+
max: bounds.max.map((v) => Math.round(v * 1000) / 1000) as vec2,
257+
};
258+
}
259+
260+
function getTexCoordArea(prim: Primitive, index: number): number {
261+
const uv = prim.getAttribute(`TEXCOORD_${index}`)!;
262+
const indices = prim.getIndices()!;
263+
264+
const a: vec2 = [0, 0];
265+
const b: vec2 = [0, 0];
266+
const c: vec2 = [0, 0];
267+
268+
let area = 0;
269+
270+
for (let i = 0, il = indices.getCount(); i < il; i += 3) {
271+
uv.getElement(indices.getScalar(i), a);
272+
uv.getElement(indices.getScalar(i + 1), b);
273+
uv.getElement(indices.getScalar(i + 2), c);
274+
area += Math.abs(0.5 * (a[0] * (b[1] - c[1]) + b[0] * (c[1] - a[1]) + c[0] * (a[1] - b[1])));
275+
}
276+
277+
return area;
278+
}

0 commit comments

Comments
 (0)