@ifc-lite/server-client 1.24.0 → 2.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/README.md +28 -1
- package/dist/client.d.ts +29 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +89 -162
- package/dist/client.js.map +1 -1
- package/dist/data-model-decoder.d.ts +4 -0
- package/dist/data-model-decoder.d.ts.map +1 -1
- package/dist/data-model-decoder.js +14 -8
- package/dist/data-model-decoder.js.map +1 -1
- package/dist/geometry-diagnostics-types.d.ts +124 -0
- package/dist/geometry-diagnostics-types.d.ts.map +1 -0
- package/dist/geometry-diagnostics-types.js +5 -0
- package/dist/geometry-diagnostics-types.js.map +1 -0
- package/dist/parquet-columns.d.ts +22 -4
- package/dist/parquet-columns.d.ts.map +1 -1
- package/dist/parquet-columns.js +32 -0
- package/dist/parquet-columns.js.map +1 -1
- package/dist/parquet-decoder.d.ts +3 -1
- package/dist/parquet-decoder.d.ts.map +1 -1
- package/dist/parquet-decoder.js +40 -70
- package/dist/parquet-decoder.js.map +1 -1
- package/dist/parquet-nullable.d.ts +26 -0
- package/dist/parquet-nullable.d.ts.map +1 -0
- package/dist/parquet-nullable.js +36 -0
- package/dist/parquet-nullable.js.map +1 -0
- package/dist/parquet-rotation.d.ts +70 -0
- package/dist/parquet-rotation.d.ts.map +1 -0
- package/dist/parquet-rotation.js +96 -0
- package/dist/parquet-rotation.js.map +1 -0
- package/dist/parquet-stream-events.d.ts +23 -0
- package/dist/parquet-stream-events.d.ts.map +1 -0
- package/dist/parquet-stream-events.js +133 -0
- package/dist/parquet-stream-events.js.map +1 -0
- package/dist/parquet-tables.d.ts +11 -1
- package/dist/parquet-tables.d.ts.map +1 -1
- package/dist/parquet-tables.js +45 -59
- package/dist/parquet-tables.js.map +1 -1
- package/dist/parse-query.d.ts +18 -0
- package/dist/parse-query.d.ts.map +1 -0
- package/dist/parse-query.js +21 -0
- package/dist/parse-query.js.map +1 -0
- package/dist/symbolic-types.d.ts +2 -0
- package/dist/symbolic-types.d.ts.map +1 -1
- package/dist/types.d.ts +8 -46
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/package.json +4 -3
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Null-safe bulk extraction for a nullable numeric Arrow column.
|
|
3
|
+
*
|
|
4
|
+
* `Vector.toArray()` returns the column's raw values buffer directly (its
|
|
5
|
+
* fast path for a primitive type), and that buffer carries whatever bits sit
|
|
6
|
+
* at a NULL row's slot — 0 in practice for a Float64/Int column written by
|
|
7
|
+
* every writer this project has observed (DuckDB, parquet-wasm), not a
|
|
8
|
+
* detectable sentinel. `data-model-decoder.ts`'s own module doc cites
|
|
9
|
+
* `toArray()` as "10-20x faster" than per-row `.get(i)` for BULK STRING
|
|
10
|
+
* extraction; that justification does not carry over to a NULLABLE numeric
|
|
11
|
+
* column, where `.get(i)` is the only call that reports a null row as `null`
|
|
12
|
+
* instead of a real-looking `0`.
|
|
13
|
+
*
|
|
14
|
+
* Two server-emitted columns are nullable `Float64` today: spatial-node
|
|
15
|
+
* `elevation` (a storey whose elevation cannot be resolved, see
|
|
16
|
+
* `apps/server/src/services/data_model/spatial.rs`) and material
|
|
17
|
+
* `thickness` (every non-layer material association — single material,
|
|
18
|
+
* material list, material constituent — always has `thickness: None`, see
|
|
19
|
+
* `apps/server/src/services/data_model/materials.rs`). Both used to decode
|
|
20
|
+
* here as `0` instead of `undefined` — a legitimate "no elevation" storey or
|
|
21
|
+
* "not a layer" material silently reading as a real, wrong measurement.
|
|
22
|
+
*/
|
|
23
|
+
export declare function nullableFloat64Column(table: {
|
|
24
|
+
getChild(name: string): unknown;
|
|
25
|
+
}, name: string): (number | null)[] | undefined;
|
|
26
|
+
//# sourceMappingURL=parquet-nullable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parquet-nullable.d.ts","sourceRoot":"","sources":["../src/parquet-nullable.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,qBAAqB,CACnC,KAAK,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAC1C,IAAI,EAAE,MAAM,GACX,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,SAAS,CAU/B"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
/**
|
|
5
|
+
* Null-safe bulk extraction for a nullable numeric Arrow column.
|
|
6
|
+
*
|
|
7
|
+
* `Vector.toArray()` returns the column's raw values buffer directly (its
|
|
8
|
+
* fast path for a primitive type), and that buffer carries whatever bits sit
|
|
9
|
+
* at a NULL row's slot — 0 in practice for a Float64/Int column written by
|
|
10
|
+
* every writer this project has observed (DuckDB, parquet-wasm), not a
|
|
11
|
+
* detectable sentinel. `data-model-decoder.ts`'s own module doc cites
|
|
12
|
+
* `toArray()` as "10-20x faster" than per-row `.get(i)` for BULK STRING
|
|
13
|
+
* extraction; that justification does not carry over to a NULLABLE numeric
|
|
14
|
+
* column, where `.get(i)` is the only call that reports a null row as `null`
|
|
15
|
+
* instead of a real-looking `0`.
|
|
16
|
+
*
|
|
17
|
+
* Two server-emitted columns are nullable `Float64` today: spatial-node
|
|
18
|
+
* `elevation` (a storey whose elevation cannot be resolved, see
|
|
19
|
+
* `apps/server/src/services/data_model/spatial.rs`) and material
|
|
20
|
+
* `thickness` (every non-layer material association — single material,
|
|
21
|
+
* material list, material constituent — always has `thickness: None`, see
|
|
22
|
+
* `apps/server/src/services/data_model/materials.rs`). Both used to decode
|
|
23
|
+
* here as `0` instead of `undefined` — a legitimate "no elevation" storey or
|
|
24
|
+
* "not a layer" material silently reading as a real, wrong measurement.
|
|
25
|
+
*/
|
|
26
|
+
export function nullableFloat64Column(table, name) {
|
|
27
|
+
const col = table.getChild(name);
|
|
28
|
+
if (!col)
|
|
29
|
+
return undefined;
|
|
30
|
+
const raw = col.toArray();
|
|
31
|
+
// The common case (no nulls in this column/payload) keeps the fast bulk path.
|
|
32
|
+
if (col.nullCount === 0)
|
|
33
|
+
return Array.from(raw);
|
|
34
|
+
return Array.from(raw, (value, i) => (col.get(i) === null ? null : value));
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=parquet-nullable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parquet-nullable.js","sourceRoot":"","sources":["../src/parquet-nullable.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,sEAAsE;AACtE,4DAA4D;AAE5D;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,qBAAqB,CACnC,KAA0C,EAC1C,IAAY;IAEZ,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,IAAI,CAGlB,CAAC;IACd,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAC3B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;IAC1B,8EAA8E;IAC9E,IAAI,GAAG,CAAC,SAAS,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;AAC7E,CAAC"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-instance rotation, shared by both Parquet transports (issues #3575, #3888).
|
|
3
|
+
*
|
|
4
|
+
* Split out of `parquet-tables.ts` to stay under that file's module-size
|
|
5
|
+
* budget (see `parquet-columns.ts` for the same reasoning applied to the
|
|
6
|
+
* shared trailing columns).
|
|
7
|
+
*
|
|
8
|
+
* The server dedupes rotated `IfcMappedItem` / shared-`IfcRepresentationMap`
|
|
9
|
+
* occurrences by storing ONE template mesh in a canonical/local frame and
|
|
10
|
+
* carrying each instance's rotation (row-major 3x3, `rot0..rot8`) alongside
|
|
11
|
+
* its `origin_x/y/z`. The optimized transport has done this since #3575 and the
|
|
12
|
+
* flat one since `-parquet-v6` (#3888); the columns, the frame and the
|
|
13
|
+
* reconstruction are identical, which is why this module is shared rather than
|
|
14
|
+
* copied. Reconstruction contract:
|
|
15
|
+
* `world = origin + R * template_position` — the SAME `origin`-only contract
|
|
16
|
+
* as before (#1841) when `R` is the identity, which is what the server emits
|
|
17
|
+
* for every instance it did not verify a rotation-aware placement for.
|
|
18
|
+
*/
|
|
19
|
+
import type { ArrowTableLike } from './parquet-tables.js';
|
|
20
|
+
/** The nine `rot0..rot8` columns, present together or not at all. */
|
|
21
|
+
export type RotationColumns = ArrayLike<number>[];
|
|
22
|
+
/**
|
|
23
|
+
* Read the rotation columns, or `undefined` when the payload does not carry
|
|
24
|
+
* them — an optimized wire-version-2 payload (#3575), or a flat `-parquet-v5`
|
|
25
|
+
* blob (#3888). Callers then fall back to the identity rotation, which is
|
|
26
|
+
* exactly the behaviour those payloads were written for.
|
|
27
|
+
*
|
|
28
|
+
* `onAbsent` says what a MISSING or short block means to the caller, in the
|
|
29
|
+
* caller's own terms rather than in one transport's version numbering.
|
|
30
|
+
* `'identity'` is the reading above. `'throw'` is for a payload whose own
|
|
31
|
+
* header states the columns are there: the optimized format's version 3 is
|
|
32
|
+
* only emitted once a non-identity rotation has actually been written
|
|
33
|
+
* (`optimized_wire_version` in `parquet_instancing.rs`), so absence there is
|
|
34
|
+
* truncated wire data, and decoding it as identity would place a genuinely
|
|
35
|
+
* rotated occurrence at the wrong orientation with no signal why.
|
|
36
|
+
*
|
|
37
|
+
* The flat transport has no such header — it cannot tell a truncated v6 mesh
|
|
38
|
+
* table from a genuine v5 one — so it passes `'identity'`, which is right for
|
|
39
|
+
* the v5 case and no worse than throwing for the other.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readRotationColumns(instanceArrow: ArrowTableLike, rowCount: number, onAbsent?: 'identity' | 'throw'): RotationColumns | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Rotate `positions` (and, when present, `normals`) IN PLACE by row `index`'s
|
|
44
|
+
* 3x3 rotation. A no-op when the row is identity (translation-only or
|
|
45
|
+
* unverified placements — the overwhelming majority of rows on most models),
|
|
46
|
+
* so this costs nothing beyond the identity check for meshes #3575 did not
|
|
47
|
+
* touch.
|
|
48
|
+
*
|
|
49
|
+
* Normals get the SAME rotation matrix as positions, not its inverse
|
|
50
|
+
* transpose — correct for a rigid (orthogonal) rotation, which is what the
|
|
51
|
+
* server's per-vertex residual check (`RECOMPOSITION_TOLERANCE_M`) verifies
|
|
52
|
+
* before emitting a non-identity row. A non-uniform scale baked into the
|
|
53
|
+
* source `IfcCartesianTransformationOperator` would need the inverse
|
|
54
|
+
* transpose for normals specifically; this matches the convention the
|
|
55
|
+
* existing GPU-instancing wire format (`ifc_lite_geometry::instancing`)
|
|
56
|
+
* already uses, so it is not a new approximation this fix introduces.
|
|
57
|
+
*
|
|
58
|
+
* `isIdentityRow` already refuses to call a row with a NaN cell "identity"
|
|
59
|
+
* (`NaN !== 1` is true, so the row fails the comparison and falls through
|
|
60
|
+
* here) — a NaN can never silently render as an unrotated placement. But
|
|
61
|
+
* multiplying it through `rotateTriplets` would still corrupt every vertex
|
|
62
|
+
* of THIS instance to NaN, uncontrolled: no error, a mesh that may vanish
|
|
63
|
+
* or wreck a shared bounding-box computation with no signal why. A row that
|
|
64
|
+
* isn't finite is malformed wire data — the same class of fault
|
|
65
|
+
* `buildMeshesFromOptimizedTables` already fails loudly on for a bad
|
|
66
|
+
* mesh/material index — so it throws here too, instead of writing NaN
|
|
67
|
+
* silently into the buffer.
|
|
68
|
+
*/
|
|
69
|
+
export declare function applyInstanceRotation(positions: Float32Array, normals: Float32Array | undefined, rot: RotationColumns, index: number): void;
|
|
70
|
+
//# sourceMappingURL=parquet-rotation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parquet-rotation.d.ts","sourceRoot":"","sources":["../src/parquet-rotation.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAG1D,qEAAqE;AACrE,MAAM,MAAM,eAAe,GAAG,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;AAKlD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CACjC,aAAa,EAAE,cAAc,EAC7B,QAAQ,EAAE,MAAM,EAChB,QAAQ,GAAE,UAAU,GAAG,OAAoB,GAC1C,eAAe,GAAG,SAAS,CAe7B;AAUD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,YAAY,GAAG,SAAS,EACjC,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,MAAM,GACZ,IAAI,CAUN"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
+
import { numericColumn } from './parquet-columns.js';
|
|
5
|
+
/** Row-major identity, hoisted so the per-mesh identity check allocates nothing. */
|
|
6
|
+
const IDENTITY = [1, 0, 0, 0, 1, 0, 0, 0, 1];
|
|
7
|
+
/**
|
|
8
|
+
* Read the rotation columns, or `undefined` when the payload does not carry
|
|
9
|
+
* them — an optimized wire-version-2 payload (#3575), or a flat `-parquet-v5`
|
|
10
|
+
* blob (#3888). Callers then fall back to the identity rotation, which is
|
|
11
|
+
* exactly the behaviour those payloads were written for.
|
|
12
|
+
*
|
|
13
|
+
* `onAbsent` says what a MISSING or short block means to the caller, in the
|
|
14
|
+
* caller's own terms rather than in one transport's version numbering.
|
|
15
|
+
* `'identity'` is the reading above. `'throw'` is for a payload whose own
|
|
16
|
+
* header states the columns are there: the optimized format's version 3 is
|
|
17
|
+
* only emitted once a non-identity rotation has actually been written
|
|
18
|
+
* (`optimized_wire_version` in `parquet_instancing.rs`), so absence there is
|
|
19
|
+
* truncated wire data, and decoding it as identity would place a genuinely
|
|
20
|
+
* rotated occurrence at the wrong orientation with no signal why.
|
|
21
|
+
*
|
|
22
|
+
* The flat transport has no such header — it cannot tell a truncated v6 mesh
|
|
23
|
+
* table from a genuine v5 one — so it passes `'identity'`, which is right for
|
|
24
|
+
* the v5 case and no worse than throwing for the other.
|
|
25
|
+
*/
|
|
26
|
+
export function readRotationColumns(instanceArrow, rowCount, onAbsent = 'identity') {
|
|
27
|
+
const cols = [];
|
|
28
|
+
for (let i = 0; i < 9; i++) {
|
|
29
|
+
const col = numericColumn(instanceArrow, `rot${i}`);
|
|
30
|
+
if (!col || col.length < rowCount) {
|
|
31
|
+
if (onAbsent === 'throw') {
|
|
32
|
+
throw new Error('Malformed optimized Parquet geometry: format version 3 requires rot0..rot8 instance columns');
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
cols.push(col);
|
|
37
|
+
}
|
|
38
|
+
return cols;
|
|
39
|
+
}
|
|
40
|
+
/** `true` when row `index`'s rotation is exactly identity (the common case). */
|
|
41
|
+
function isIdentityRow(rot, index) {
|
|
42
|
+
for (let i = 0; i < 9; i++) {
|
|
43
|
+
if (rot[i][index] !== IDENTITY[i])
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Rotate `positions` (and, when present, `normals`) IN PLACE by row `index`'s
|
|
50
|
+
* 3x3 rotation. A no-op when the row is identity (translation-only or
|
|
51
|
+
* unverified placements — the overwhelming majority of rows on most models),
|
|
52
|
+
* so this costs nothing beyond the identity check for meshes #3575 did not
|
|
53
|
+
* touch.
|
|
54
|
+
*
|
|
55
|
+
* Normals get the SAME rotation matrix as positions, not its inverse
|
|
56
|
+
* transpose — correct for a rigid (orthogonal) rotation, which is what the
|
|
57
|
+
* server's per-vertex residual check (`RECOMPOSITION_TOLERANCE_M`) verifies
|
|
58
|
+
* before emitting a non-identity row. A non-uniform scale baked into the
|
|
59
|
+
* source `IfcCartesianTransformationOperator` would need the inverse
|
|
60
|
+
* transpose for normals specifically; this matches the convention the
|
|
61
|
+
* existing GPU-instancing wire format (`ifc_lite_geometry::instancing`)
|
|
62
|
+
* already uses, so it is not a new approximation this fix introduces.
|
|
63
|
+
*
|
|
64
|
+
* `isIdentityRow` already refuses to call a row with a NaN cell "identity"
|
|
65
|
+
* (`NaN !== 1` is true, so the row fails the comparison and falls through
|
|
66
|
+
* here) — a NaN can never silently render as an unrotated placement. But
|
|
67
|
+
* multiplying it through `rotateTriplets` would still corrupt every vertex
|
|
68
|
+
* of THIS instance to NaN, uncontrolled: no error, a mesh that may vanish
|
|
69
|
+
* or wreck a shared bounding-box computation with no signal why. A row that
|
|
70
|
+
* isn't finite is malformed wire data — the same class of fault
|
|
71
|
+
* `buildMeshesFromOptimizedTables` already fails loudly on for a bad
|
|
72
|
+
* mesh/material index — so it throws here too, instead of writing NaN
|
|
73
|
+
* silently into the buffer.
|
|
74
|
+
*/
|
|
75
|
+
export function applyInstanceRotation(positions, normals, rot, index) {
|
|
76
|
+
if (isIdentityRow(rot, index))
|
|
77
|
+
return;
|
|
78
|
+
const r = rot.map((col) => col[index]);
|
|
79
|
+
if (!r.every(Number.isFinite)) {
|
|
80
|
+
throw new Error(`Malformed Parquet geometry: non-finite rotation value for row ${index} (rot=[${r.join(', ')}])`);
|
|
81
|
+
}
|
|
82
|
+
rotateTriplets(positions, r);
|
|
83
|
+
if (normals)
|
|
84
|
+
rotateTriplets(normals, r);
|
|
85
|
+
}
|
|
86
|
+
function rotateTriplets(buf, r) {
|
|
87
|
+
for (let v = 0; v < buf.length; v += 3) {
|
|
88
|
+
const x = buf[v];
|
|
89
|
+
const y = buf[v + 1];
|
|
90
|
+
const z = buf[v + 2];
|
|
91
|
+
buf[v] = r[0] * x + r[1] * y + r[2] * z;
|
|
92
|
+
buf[v + 1] = r[3] * x + r[4] * y + r[5] * z;
|
|
93
|
+
buf[v + 2] = r[6] * x + r[7] * y + r[8] * z;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=parquet-rotation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parquet-rotation.js","sourceRoot":"","sources":["../src/parquet-rotation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ParquetBatch, ParquetStreamResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Raised when an SSE parse stream ends with no terminal event.
|
|
4
|
+
*
|
|
5
|
+
* Shared by `parseStream` and the Parquet stream reader below so the two
|
|
6
|
+
* cannot drift apart by an article. The wording has to hold on BOTH paths,
|
|
7
|
+
* which it does: this reader throws on an `error` event at its own
|
|
8
|
+
* `case 'error'`, so it can only reach its `!stats || !metadata` check in the
|
|
9
|
+
* same state `parseStream`'s `!terminated` describes, the stream stopped
|
|
10
|
+
* without ever saying why. Not re-exported from the package index: it is an
|
|
11
|
+
* internal guarantee about two call sites, not part of the published surface.
|
|
12
|
+
*/
|
|
13
|
+
export declare const STREAM_ENDED_WITHOUT_TERMINAL_EVENT = "Stream ended without a complete event (connection dropped or the server failed mid-parse)";
|
|
14
|
+
/**
|
|
15
|
+
* Drain a `parquet-stream` SSE response, decoding each batch and handing it to
|
|
16
|
+
* `onBatch` as it arrives.
|
|
17
|
+
*
|
|
18
|
+
* @param response - An OK response whose body is the SSE stream.
|
|
19
|
+
* @param onBatch - Called once per decoded batch, for immediate rendering.
|
|
20
|
+
* @param startedAt - `performance.now()` at request time, for the timing log.
|
|
21
|
+
*/
|
|
22
|
+
export declare function consumeParquetStream(response: Response, onBatch: (batch: ParquetBatch) => void, startedAt: number): Promise<ParquetStreamResult>;
|
|
23
|
+
//# sourceMappingURL=parquet-stream-events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parquet-stream-events.d.ts","sourceRoot":"","sources":["../src/parquet-stream-events.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAEV,YAAY,EAEZ,mBAAmB,EAGpB,MAAM,YAAY,CAAC;AAEpB;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mCAAmC,8FAC6C,CAAC;AAE9F;;;;;;;GAOG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,EACtC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,mBAAmB,CAAC,CA4G9B"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/**
|
|
5
|
+
* Reading the SSE body of `POST /api/v1/parse/parquet-stream`.
|
|
6
|
+
*
|
|
7
|
+
* Its own module because there are now TWO requests that answer with that
|
|
8
|
+
* stream and they must be consumed identically (issue #3901): the hash-only
|
|
9
|
+
* probe, which sends no file at all and replays from cache, and the multipart
|
|
10
|
+
* upload it falls back to. The server guarantees a hit replays the same events
|
|
11
|
+
* a live parse emits; duplicating this loop per call site is how that
|
|
12
|
+
* guarantee stops being observable on the client.
|
|
13
|
+
*/
|
|
14
|
+
import { decodeParquetGeometry } from './parquet-decoder.js';
|
|
15
|
+
/**
|
|
16
|
+
* Raised when an SSE parse stream ends with no terminal event.
|
|
17
|
+
*
|
|
18
|
+
* Shared by `parseStream` and the Parquet stream reader below so the two
|
|
19
|
+
* cannot drift apart by an article. The wording has to hold on BOTH paths,
|
|
20
|
+
* which it does: this reader throws on an `error` event at its own
|
|
21
|
+
* `case 'error'`, so it can only reach its `!stats || !metadata` check in the
|
|
22
|
+
* same state `parseStream`'s `!terminated` describes, the stream stopped
|
|
23
|
+
* without ever saying why. Not re-exported from the package index: it is an
|
|
24
|
+
* internal guarantee about two call sites, not part of the published surface.
|
|
25
|
+
*/
|
|
26
|
+
export const STREAM_ENDED_WITHOUT_TERMINAL_EVENT = 'Stream ended without a complete event (connection dropped or the server failed mid-parse)';
|
|
27
|
+
/**
|
|
28
|
+
* Drain a `parquet-stream` SSE response, decoding each batch and handing it to
|
|
29
|
+
* `onBatch` as it arrives.
|
|
30
|
+
*
|
|
31
|
+
* @param response - An OK response whose body is the SSE stream.
|
|
32
|
+
* @param onBatch - Called once per decoded batch, for immediate rendering.
|
|
33
|
+
* @param startedAt - `performance.now()` at request time, for the timing log.
|
|
34
|
+
*/
|
|
35
|
+
export async function consumeParquetStream(response, onBatch, startedAt) {
|
|
36
|
+
if (!response.body) {
|
|
37
|
+
throw new Error('No response body for streaming');
|
|
38
|
+
}
|
|
39
|
+
const reader = response.body.getReader();
|
|
40
|
+
const decoder = new TextDecoder();
|
|
41
|
+
let buffer = '';
|
|
42
|
+
let cache_key = '';
|
|
43
|
+
let total_meshes = 0;
|
|
44
|
+
let stats = null;
|
|
45
|
+
let metadata = null;
|
|
46
|
+
let symbolic_data;
|
|
47
|
+
// A thrown 'error' event (or any other exception mid-loop) must not
|
|
48
|
+
// leave the reader locked on `response.body` — mirrors the try/finally
|
|
49
|
+
// `parseStream` already uses for the same SSE-reading shape.
|
|
50
|
+
try {
|
|
51
|
+
while (true) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done)
|
|
54
|
+
break;
|
|
55
|
+
buffer += decoder.decode(value, { stream: true });
|
|
56
|
+
// Process complete SSE events
|
|
57
|
+
const lines = buffer.split('\n');
|
|
58
|
+
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
59
|
+
for (const line of lines) {
|
|
60
|
+
if (!line.startsWith('data:'))
|
|
61
|
+
continue;
|
|
62
|
+
const jsonStr = line.slice(5).trim();
|
|
63
|
+
if (!jsonStr)
|
|
64
|
+
continue;
|
|
65
|
+
try {
|
|
66
|
+
const event = JSON.parse(jsonStr);
|
|
67
|
+
switch (event.type) {
|
|
68
|
+
case 'start':
|
|
69
|
+
cache_key = event.cache_key;
|
|
70
|
+
console.log(`[client] Stream started: ${event.total_estimate} entities, cache_key: ${cache_key.substring(0, 16)}...`);
|
|
71
|
+
break;
|
|
72
|
+
case 'progress':
|
|
73
|
+
// Progress events can be used for UI feedback
|
|
74
|
+
break;
|
|
75
|
+
case 'batch': {
|
|
76
|
+
const decodeStart = performance.now();
|
|
77
|
+
// Decode base64 Parquet data
|
|
78
|
+
const binaryStr = atob(event.data);
|
|
79
|
+
const bytes = new Uint8Array(binaryStr.length);
|
|
80
|
+
for (let i = 0; i < binaryStr.length; i++) {
|
|
81
|
+
bytes[i] = binaryStr.charCodeAt(i);
|
|
82
|
+
}
|
|
83
|
+
// Decode Parquet to meshes
|
|
84
|
+
const meshes = await decodeParquetGeometry(bytes.buffer);
|
|
85
|
+
const decodeTime = performance.now() - decodeStart;
|
|
86
|
+
total_meshes += meshes.length;
|
|
87
|
+
console.log(`[client] Batch #${event.batch_number}: ${meshes.length} meshes, decode: ${decodeTime.toFixed(0)}ms`);
|
|
88
|
+
// Call the batch callback for immediate rendering
|
|
89
|
+
onBatch({
|
|
90
|
+
meshes,
|
|
91
|
+
batch_number: event.batch_number,
|
|
92
|
+
decode_time_ms: decodeTime,
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case 'complete': {
|
|
97
|
+
stats = event.stats;
|
|
98
|
+
metadata = event.metadata;
|
|
99
|
+
symbolic_data = event.symbolic_data;
|
|
100
|
+
const totalTime = performance.now() - startedAt;
|
|
101
|
+
console.log(`[client] Stream complete: ${total_meshes} meshes in ${totalTime.toFixed(0)}ms`);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
case 'error':
|
|
105
|
+
throw new Error(`Stream error: ${event.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
if (e instanceof SyntaxError) {
|
|
110
|
+
console.warn('[client] Failed to parse SSE event:', jsonStr);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
throw e;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
finally {
|
|
120
|
+
reader.releaseLock();
|
|
121
|
+
}
|
|
122
|
+
if (!stats || !metadata) {
|
|
123
|
+
throw new Error(STREAM_ENDED_WITHOUT_TERMINAL_EVENT);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
cache_key,
|
|
127
|
+
total_meshes,
|
|
128
|
+
stats,
|
|
129
|
+
metadata,
|
|
130
|
+
symbolic_data,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=parquet-stream-events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parquet-stream-events.js","sourceRoot":"","sources":["../src/parquet-stream-events.ts"],"names":[],"mappings":"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"}
|
package/dist/parquet-tables.d.ts
CHANGED
|
@@ -24,10 +24,19 @@ export interface ArrowTableLike {
|
|
|
24
24
|
getChild(name: string): ArrowColumnLike | null | undefined;
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* Rebuild `MeshData[]` from the standard
|
|
27
|
+
* Rebuild `MeshData[]` from the standard three-table layout.
|
|
28
28
|
*
|
|
29
29
|
* Positions/normals are already Y-up metres — the server applies the axis swap
|
|
30
30
|
* once, in `services::axis` (issue #1841), so every transport agrees.
|
|
31
|
+
*
|
|
32
|
+
* "Non-instanced" until `-parquet-v6` (issue #3888): several mesh rows can now
|
|
33
|
+
* name the SAME `vertex_start`/`index_start` block, each placed by its own
|
|
34
|
+
* `origin_x/y/z` plus a `rot0..rot8` rotation (`world = origin + R * p`) — the
|
|
35
|
+
* rotation-aware sharing the optimized transport has carried since #3575. Two
|
|
36
|
+
* things follow, and both are what keeps a `-parquet-v5` blob decoding here
|
|
37
|
+
* unchanged: the rotation columns are ABSENT on v5, and absent means identity;
|
|
38
|
+
* and `origin` was zero on every v5 flat row, so folding it in was a no-op
|
|
39
|
+
* there and is load-bearing here.
|
|
31
40
|
*/
|
|
32
41
|
export declare function buildMeshesFromTables(meshArrow: ArrowTableLike, vertexArrow: ArrowTableLike, indexArrow: ArrowTableLike): MeshData[];
|
|
33
42
|
/** Tables + header flags of the optimized (instanced, quantized) layout. */
|
|
@@ -41,6 +50,7 @@ export interface OptimizedTables {
|
|
|
41
50
|
hasNormals: boolean;
|
|
42
51
|
/** Quantization divisor: metres = quantized / vertexMultiplier. */
|
|
43
52
|
vertexMultiplier: number;
|
|
53
|
+
wireVersion: 2 | 3;
|
|
44
54
|
}
|
|
45
55
|
/**
|
|
46
56
|
* Rebuild `MeshData[]` from the optimized instanced layout.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parquet-tables.d.ts","sourceRoot":"","sources":["../src/parquet-tables.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"parquet-tables.d.ts","sourceRoot":"","sources":["../src/parquet-tables.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAI3C;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC;IAC7B,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,GAAG,SAAS,CAAC;CAC5D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,cAAc,EACzB,WAAW,EAAE,cAAc,EAC3B,UAAU,EAAE,cAAc,GACzB,QAAQ,EAAE,CAyIZ;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,aAAa,EAAE,cAAc,CAAC;IAC9B,SAAS,EAAE,cAAc,CAAC;IAC1B,aAAa,EAAE,cAAc,CAAC;IAC9B,WAAW,EAAE,cAAc,CAAC;IAC5B,UAAU,EAAE,cAAc,CAAC;IAC3B,+EAA+E;IAC/E,UAAU,EAAE,OAAO,CAAC;IACpB,mEAAmE;IACnE,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,eAAe,GAAG,QAAQ,EAAE,CAiLlF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,MAAM,EAAE,GAAG,WAAW,GAC9B,YAAY,CA0Cd"}
|
package/dist/parquet-tables.js
CHANGED
|
@@ -1,44 +1,22 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
import { meshColumns, numericColumn,
|
|
4
|
+
import { meshColumns, numericColumn, transformFields } from './parquet-columns.js';
|
|
5
|
+
import { applyInstanceRotation, readRotationColumns } from './parquet-rotation.js';
|
|
5
6
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `origin` is omitted when the frame IS the world origin and `geometry_class`
|
|
9
|
-
* when it is 0 (occurrence), so a world-baked mesh decodes to exactly the shape
|
|
10
|
-
* it had before these columns existed — and to the same shape on every
|
|
11
|
-
* transport (JSON, standard Parquet, optimized Parquet).
|
|
12
|
-
*/
|
|
13
|
-
function transformFields(index, cols) {
|
|
14
|
-
// A usable column can still carry a non-finite VALUE at this row. `||` alone
|
|
15
|
-
// misses it: an all-NaN triplet is already dropped, but a PARTIAL one
|
|
16
|
-
// (`[NaN, 5, 0]`) is truthy and the NaN would poison bounds math. Not a
|
|
17
|
-
// throw -- this file throws only for STRUCTURAL malformation.
|
|
18
|
-
const ox = cols.originX?.[index];
|
|
19
|
-
const oy = cols.originY?.[index];
|
|
20
|
-
const oz = cols.originZ?.[index];
|
|
21
|
-
const originFinite = Number.isFinite(ox) && Number.isFinite(oy) && Number.isFinite(oz);
|
|
22
|
-
const origin = originFinite && (ox || oy || oz) ? [ox, oy, oz] : undefined;
|
|
23
|
-
const geometry_class = cols.geometryClass?.[index] || undefined;
|
|
24
|
-
// Sentinel, not null (#3215): a nullable column's values buffer is undefined
|
|
25
|
-
// at null rows and parquet-wasm 0.7.x leaks the NEIGHBOURING row's id into it
|
|
26
|
-
// -- a material-less mesh decoded as `material_id: 902`, a real-looking id
|
|
27
|
-
// for another entity. Non-nullable means no validity bitmap to leak.
|
|
28
|
-
const geometry_item_id = readSourceId(cols.geometryItemId, index);
|
|
29
|
-
const material_id = readSourceId(cols.materialId, index);
|
|
30
|
-
return {
|
|
31
|
-
...(origin ? { origin } : {}),
|
|
32
|
-
...(geometry_class ? { geometry_class } : {}),
|
|
33
|
-
...(geometry_item_id ? { geometry_item_id } : {}),
|
|
34
|
-
...(material_id ? { material_id } : {}),
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Rebuild `MeshData[]` from the standard (non-instanced) three-table layout.
|
|
7
|
+
* Rebuild `MeshData[]` from the standard three-table layout.
|
|
39
8
|
*
|
|
40
9
|
* Positions/normals are already Y-up metres — the server applies the axis swap
|
|
41
10
|
* once, in `services::axis` (issue #1841), so every transport agrees.
|
|
11
|
+
*
|
|
12
|
+
* "Non-instanced" until `-parquet-v6` (issue #3888): several mesh rows can now
|
|
13
|
+
* name the SAME `vertex_start`/`index_start` block, each placed by its own
|
|
14
|
+
* `origin_x/y/z` plus a `rot0..rot8` rotation (`world = origin + R * p`) — the
|
|
15
|
+
* rotation-aware sharing the optimized transport has carried since #3575. Two
|
|
16
|
+
* things follow, and both are what keeps a `-parquet-v5` blob decoding here
|
|
17
|
+
* unchanged: the rotation columns are ABSENT on v5, and absent means identity;
|
|
18
|
+
* and `origin` was zero on every v5 flat row, so folding it in was a no-op
|
|
19
|
+
* there and is load-bearing here.
|
|
42
20
|
*/
|
|
43
21
|
export function buildMeshesFromTables(meshArrow, vertexArrow, indexArrow) {
|
|
44
22
|
// Extract columns from mesh table
|
|
@@ -91,6 +69,10 @@ export function buildMeshesFromTables(meshArrow, vertexArrow, indexArrow) {
|
|
|
91
69
|
// from servers predating them, where origin defaults to [0,0,0] and the
|
|
92
70
|
// source ids simply do not appear.
|
|
93
71
|
const cols = meshColumns(meshArrow, meshCount);
|
|
72
|
+
// Absent on every pre-#3888 payload. The flat transport has no version byte
|
|
73
|
+
// to tell a truncated v6 from a genuine v5, so absence reads as the identity
|
|
74
|
+
// it is on a v5 blob (see `readRotationColumns`).
|
|
75
|
+
const rotationCols = readRotationColumns(meshArrow, meshCount, 'identity');
|
|
94
76
|
const meshes = new Array(meshCount);
|
|
95
77
|
// Only consume the additive origin/geometry_class columns when all three
|
|
96
78
|
// origin components are present AND parallel to the mesh rows — a short or
|
|
@@ -112,10 +94,8 @@ export function buildMeshesFromTables(meshArrow, vertexArrow, indexArrow) {
|
|
|
112
94
|
`(vertexStart=${vertexStart}, vertexCount=${vertexCount}, vertices=${posX.length}; ` +
|
|
113
95
|
`indexStart=${indexStart}, indexCount=${indexCount}, triangles=${idx0.length})`);
|
|
114
96
|
}
|
|
115
|
-
// Reconstruct interleaved positions from columnar format
|
|
116
|
-
//
|
|
117
|
-
// Server already transforms: X stays same, new Y = old Z, new Z = -old Y
|
|
118
|
-
// So we just copy directly without per-vertex transformation
|
|
97
|
+
// Reconstruct interleaved positions from columnar format. The server
|
|
98
|
+
// already applies the Z-up -> Y-up swap, so this just copies through.
|
|
119
99
|
const positions = new Float32Array(vertexCount * 3);
|
|
120
100
|
for (let v = 0; v < vertexCount; v++) {
|
|
121
101
|
const srcIdx = vertexStart + v;
|
|
@@ -123,7 +103,7 @@ export function buildMeshesFromTables(meshArrow, vertexArrow, indexArrow) {
|
|
|
123
103
|
positions[v * 3 + 1] = posY[srcIdx];
|
|
124
104
|
positions[v * 3 + 2] = posZ[srcIdx];
|
|
125
105
|
}
|
|
126
|
-
// Reconstruct interleaved normals (also pre-transformed server-side)
|
|
106
|
+
// Reconstruct interleaved normals (also pre-transformed server-side).
|
|
127
107
|
const normals = new Float32Array(vertexCount * 3);
|
|
128
108
|
for (let v = 0; v < vertexCount; v++) {
|
|
129
109
|
const srcIdx = vertexStart + v;
|
|
@@ -131,9 +111,13 @@ export function buildMeshesFromTables(meshArrow, vertexArrow, indexArrow) {
|
|
|
131
111
|
normals[v * 3 + 1] = normY[srcIdx];
|
|
132
112
|
normals[v * 3 + 2] = normZ[srcIdx];
|
|
133
113
|
}
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
114
|
+
// Rotate the shared block onto THIS occurrence before `origin` translates
|
|
115
|
+
// it (`world = origin + R * p`). A no-op on an identity row, which is every
|
|
116
|
+
// row of a v5 payload and every unshared row of a v6 one.
|
|
117
|
+
if (rotationCols)
|
|
118
|
+
applyInstanceRotation(positions, normals, rotationCols, i);
|
|
119
|
+
// Reconstruct triangle indices from columnar format.
|
|
120
|
+
const triangleCount = indexCount / 3, triangleStart = indexStart / 3;
|
|
137
121
|
const indices = new Uint32Array(indexCount);
|
|
138
122
|
for (let t = 0; t < triangleCount; t++) {
|
|
139
123
|
const srcIdx = triangleStart + t;
|
|
@@ -222,6 +206,9 @@ export function buildMeshesFromOptimizedTables(tables) {
|
|
|
222
206
|
const cols = meshColumns(instanceArrow, instanceCount);
|
|
223
207
|
const meshes = new Array(instanceCount);
|
|
224
208
|
const dequantMultiplier = 1.0 / vertexMultiplier;
|
|
209
|
+
// Wire version 3 states the columns are there (#3575), so absence is
|
|
210
|
+
// truncated data, not an older payload; version 2 predates them.
|
|
211
|
+
const rotationCols = readRotationColumns(instanceArrow, instanceCount, tables.wireVersion === 3 ? 'throw' : 'identity');
|
|
225
212
|
// Additive per-instance origin/geometry_class columns (issue #1841): consume
|
|
226
213
|
// only when present AND parallel to the instance rows.
|
|
227
214
|
for (let i = 0; i < instanceCount; i++) {
|
|
@@ -249,9 +236,7 @@ export function buildMeshesFromOptimizedTables(tables) {
|
|
|
249
236
|
`vertices=${vertexX.length}; indexOffset=${indexOffset}, indexCount=${indexCount}, ` +
|
|
250
237
|
`indices=${indices.length})`);
|
|
251
238
|
}
|
|
252
|
-
// Dequantize
|
|
253
|
-
// OPTIMIZATION: Z-up to Y-up transform is now done server-side for optimized format too
|
|
254
|
-
// Server already transforms before quantization, so we just dequantize directly
|
|
239
|
+
// Dequantize (the server already applied Z-up -> Y-up before quantizing).
|
|
255
240
|
const positions = new Float32Array(vertexCount * 3);
|
|
256
241
|
for (let v = 0; v < vertexCount; v++) {
|
|
257
242
|
const srcIdx = vertexOffset + v;
|
|
@@ -264,25 +249,26 @@ export function buildMeshesFromOptimizedTables(tables) {
|
|
|
264
249
|
for (let j = 0; j < indexCount; j++) {
|
|
265
250
|
meshIndicesArray[j] = indices[indexOffset + j];
|
|
266
251
|
}
|
|
267
|
-
//
|
|
268
|
-
|
|
252
|
+
// Server-provided normals, read BEFORE rotating (#3575) so a flat
|
|
253
|
+
// recompute below, when absent, starts from already-rotated positions.
|
|
254
|
+
let providedNormals;
|
|
269
255
|
if (hasNormals && normalX && normalY && normalZ) {
|
|
270
|
-
|
|
256
|
+
providedNormals = new Float32Array(vertexCount * 3);
|
|
271
257
|
for (let v = 0; v < vertexCount; v++) {
|
|
272
258
|
const srcIdx = vertexOffset + v;
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
259
|
+
providedNormals[v * 3] = normalX[srcIdx];
|
|
260
|
+
providedNormals[v * 3 + 1] = normalY[srcIdx];
|
|
261
|
+
providedNormals[v * 3 + 2] = normalZ[srcIdx];
|
|
276
262
|
}
|
|
277
263
|
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
// Convert byte colors to float [0-1]. `
|
|
283
|
-
//
|
|
284
|
-
//
|
|
285
|
-
//
|
|
264
|
+
// Rotate into this instance's own frame (#3575); no-op for identity rows.
|
|
265
|
+
if (rotationCols)
|
|
266
|
+
applyInstanceRotation(positions, providedNormals, rotationCols, i);
|
|
267
|
+
const normals = providedNormals ?? computeFlatNormals(positions, meshIndicesArray);
|
|
268
|
+
// Convert byte colors to float [0-1]. `origin` (not baked in, for f32
|
|
269
|
+
// precision at building scale) carries the remaining translation: world
|
|
270
|
+
// = origin + position, same contract as the standard path (#1841),
|
|
271
|
+
// extended with the rotation already applied to `positions` above.
|
|
286
272
|
meshes[i] = {
|
|
287
273
|
express_id: entityIds[i],
|
|
288
274
|
ifc_type: ifcTypes?.get(i) ?? 'Unknown',
|