Reading an accessor
64-bitReads one accessor's numbers, with its sparse part, into a typed array.
normalized: v ÷ 127, 255, 32767 or 65535 (signed values not below −1)
32-bit floats are copied as stored into a Float32Array: that is the format's own width. Normalized integers are divided in 64-bit and then stored in the same Float32Array, which rounds a 16-bit value's quotient to 32-bit.
const accessor = (ai, what = 'an accessor') => {
if (accCache.has(ai)) return accCache.get(ai);
const a = accIn[ai];
if (!isObj(a)) throw damaged(what + ' names a missing accessor');
const ct = COMP[a.componentType];
const nc = NCOMP[a.type];
if (!ct || !nc) throw damaged('accessor ' + ai + ' has an unknown type');
const count = a.count;
if (!Number.isInteger(count) || count < 1) throw damaged('accessor ' + ai + ' has a bad count');
const n = count * nc;
if (n > L.accessor) throw new GltfError('too_big', t('gltf.too_big'));
budget -= n;
if (budget < 0) throw new GltfError('too_big', t('gltf.too_big'));
const [size, getter, Arr] = ct;
const norm = a.normalized === true && a.componentType !== 5126 && a.componentType !== 5125;
const out = norm || a.componentType === 5126 ? new Float32Array(n) : new Arr(n);
const scale = !norm ? 1 : { 5120: 127, 5121: 255, 5122: 32767, 5123: 65535 }[a.componentType];
const signed = a.componentType === 5120 || a.componentType === 5122;
// matrix columns of 1- and 2-byte components start on 4-byte boundaries
const rows = a.type === 'MAT2' ? 2 : a.type === 'MAT3' ? 3 : a.type === 'MAT4' ? 4 : nc;
const cols = nc / rows;
const colBytes = (a.type === 'MAT2' && size === 1) || (a.type === 'MAT3' && size <= 2) ? Math.ceil(rows * size / 4) * 4 : rows * size;
const elBytes = cols * colBytes;
const readInto = (bytes, stride, off, cnt, put) => {
const st = stride || elBytes;
if (!Number.isInteger(off) || off < 0 || off % size || (cnt && off + st * (cnt - 1) + elBytes > bytes.length)) throw damaged('accessor ' + ai + ' runs past its buffer view');
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
for (let i = 0; i < cnt; i++) {
for (let c = 0; c < cols; c++) for (let r = 0; r < rows; r++) {
let v = dv[getter](off + i * st + c * colBytes + r * size, true);
if (norm) v = signed ? Math.max(v / scale, -1) : v / scale;
put(i, c * rows + r, v);
}
}
};
if (a.bufferView !== undefined) {
const v = views[idx(a.bufferView, views.length)];
if (!v) throw damaged('accessor ' + ai + ' names a missing buffer view');
readInto(v.bytes, v.stride, a.byteOffset ?? 0, count, (i, k, val) => { out[i * nc + k] = val; });
}
if (a.sparse !== undefined) {
const s = a.sparse;
if (!isObj(s) || !Number.isInteger(s.count) || s.count < 1 || s.count > count || !isObj(s.indices) || !isObj(s.values)) throw damaged('accessor ' + ai + ' has a broken sparse part');
const it = COMP[s.indices.componentType];
if (!it || ![5121, 5123, 5125].includes(s.indices.componentType)) throw damaged('accessor ' + ai + ' has bad sparse indices');
const iv = views[idx(s.indices.bufferView, views.length)];
const vv = views[idx(s.values.bufferView, views.length)];
if (!iv || !vv) throw damaged('accessor ' + ai + ' sparse part names a missing buffer view');
const io = s.indices.byteOffset ?? 0;
if (!Number.isInteger(io) || io < 0 || io + s.count * it[0] > iv.bytes.length) throw damaged('accessor ' + ai + ' sparse indices run past their view');
const idv = new DataView(iv.bytes.buffer, iv.bytes.byteOffset, iv.bytes.byteLength);
const at = new Uint32Array(s.count);
for (let i = 0; i < s.count; i++) {
at[i] = idv[it[1]](io + i * it[0], true);
if (at[i] >= count || (i && at[i] <= at[i - 1])) throw damaged('accessor ' + ai + ' sparse indices are out of order or range');
}
readInto(vv.bytes, 0, s.values.byteOffset ?? 0, s.count, (i, k, val) => { out[at[i] * nc + k] = val; });
}
const r = { data: out, count, nc, type: a.type, componentType: a.componentType, normalized: norm };
accCache.set(ai, r);
return r;
};