Skip to content

Commit 609b339

Browse files
committed
chore(pointcloud): Add unit test for adaptivePointSize
1 parent 9c00518 commit 609b339

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import assert from 'assert';
2+
import PointCloudNode, { buildVoxelKey } from 'Core/PointCloudNode';
3+
import { computeVisibilityTextureData } from 'Utils/PointCloudUtils';
4+
5+
function createNode(depth, x, y, z) {
6+
const node = new PointCloudNode(depth);
7+
node.x = x;
8+
node.y = y;
9+
node.z = z;
10+
node.voxelKey = buildVoxelKey(depth, x, y, z);
11+
// Override setOBBes so node.add() doesn't require a real source/crs.
12+
node.setOBBes = () => {};
13+
return node;
14+
}
15+
16+
// Counts the number of set bits in `mask` at positions 0..maxIndex (inclusive).
17+
// Mirrors the GLSL helper numberOfOnes() used in PointsVS.glsl.
18+
function numberOfOnes(mask, maxIndex) {
19+
let count = 0;
20+
for (let i = 0; i <= maxIndex; i++) {
21+
if ((mask >> i) & 1) { count++; }
22+
}
23+
return count;
24+
}
25+
26+
describe('Adaptive point size', function () {
27+
describe('computeVisibilityTextureData', function () {
28+
it('Single visible root → nodeToIndex has one entry', function () {
29+
const root = createNode(0, 0, 0, 0);
30+
31+
const { data, nodeToIndex } = computeVisibilityTextureData(root, [root]);
32+
33+
// BFS should have assigned index 0 to the root.
34+
assert.strictEqual(nodeToIndex.size, 1);
35+
assert.strictEqual(nodeToIndex.get(root), 0);
36+
37+
// Output buffer: 1 node × 4 channels.
38+
assert.strictEqual(data.length, 4);
39+
40+
// No visible children → all channels 0.
41+
assert.strictEqual(data[0], 0, 'R channel (child mask) should be 0');
42+
assert.strictEqual(data[1], 0, 'G channel (offset high byte) should be 0');
43+
assert.strictEqual(data[2], 0, 'B channel (offset low byte) should be 0');
44+
});
45+
46+
it('3-level tree → correct BFS indices, mask and offset', function () {
47+
const root = createNode(0, 0, 0, 0);
48+
// childA : potree index 0 (dx=0, dy=0, dz=0)
49+
const childA = createNode(1, 0, 0, 0);
50+
// childB : potree index 4 (dx=1, dy=0, dz=0)
51+
const childB = createNode(1, 1, 0, 0);
52+
// grandChildren of childA (potree indices 0 and 1 relative to childA)
53+
const childAA = createNode(2, 0, 0, 0);
54+
const childAB = createNode(2, 0, 0, 1);
55+
56+
root.add(childA);
57+
root.add(childB);
58+
childA.add(childAA);
59+
childA.add(childAB);
60+
61+
const visibleNodes = [root, childA, childAA, childAB, childB];
62+
const { data, nodeToIndex } = computeVisibilityTextureData(root, visibleNodes);
63+
64+
// BFS assigns root=0, childA=1, childB=2.
65+
assert.equal(nodeToIndex.get(root), 0);
66+
assert.equal(nodeToIndex.get(childA), 1);
67+
assert.equal(nodeToIndex.get(childB), 2);
68+
assert.equal(nodeToIndex.get(childAA), 3, 'childAA BFS index');
69+
assert.equal(nodeToIndex.get(childAB), 4, 'childAB BFS index');
70+
71+
// Root encoding:
72+
// mask = (1 << 0) | (1 << 4) = 1 + 16 = 17
73+
// minOffset = min(1-0, 2-0) = 1
74+
assert.equal(data[0], 17, 'root R channel (child mask)');
75+
assert.equal(data[1], 0, 'root G channel (offset high byte)');
76+
assert.equal(data[2], 1, 'root B channel (offset low byte = 1)');
77+
78+
// childA: both childAA (potree idx 0) and childAB (potree idx 1) are visible.
79+
// mask = (1 << 0) | (1 << 1) = 3
80+
// minOffset = min(3-1, 4-1) = 2
81+
assert.equal(data[4], 3, 'childA R channel (mask for childAA and childAB)');
82+
assert.equal(data[5], 0, 'childA G channel (offset high byte)');
83+
assert.equal(data[6], 2, 'childA B channel (offset low byte = 2)');
84+
85+
// childB has no visible sub-children → its channels remain 0.
86+
assert.equal(data[8], 0, 'childB R channel');
87+
88+
// grandChildren have no visible sub-children.
89+
assert.strictEqual(data[12], 0, 'childAA R channel');
90+
assert.strictEqual(data[16], 0, 'childAB R channel');
91+
92+
// --- Octree reconstruction from texture data ---
93+
// Invert nodeToIndex to get indexToNode
94+
const indexToNode = new Map();
95+
for (const [node, idx] of nodeToIndex) {
96+
indexToNode.set(idx, node);
97+
}
98+
99+
// For each visible node, decode its children list from the texture
100+
const reconstructed = new Map();
101+
for (const [node, nodeIdx] of nodeToIndex) {
102+
const mask = data[nodeIdx * 4];
103+
const minOffset = (data[nodeIdx * 4 + 1] << 8) | data[nodeIdx * 4 + 2];
104+
const children = [];
105+
for (let bit = 0; bit < 8; bit++) {
106+
if ((mask >> bit) & 1) {
107+
const childOffset = numberOfOnes(mask, bit - 1);
108+
const childIdx = nodeIdx + minOffset + childOffset;
109+
children.push(indexToNode.get(childIdx));
110+
}
111+
}
112+
reconstructed.set(node, children);
113+
}
114+
115+
assert.deepStrictEqual(reconstructed.get(root), [childA, childB], 'reconstructed root children');
116+
assert.deepStrictEqual(reconstructed.get(childA), [childAA, childAB], 'reconstructed childA children');
117+
assert.deepStrictEqual(reconstructed.get(childB), [], 'reconstructed childB has no children');
118+
assert.deepStrictEqual(reconstructed.get(childAA), [], 'reconstructed childAA has no children');
119+
assert.deepStrictEqual(reconstructed.get(childAB), [], 'reconstructed childAB has no children');
120+
});
121+
});
122+
});

0 commit comments

Comments
 (0)