react-babylon-map 0.0.1
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/.claude/settings.local.json +78 -0
- package/demo.html +161 -0
- package/dist/cjs/main.js +520 -0
- package/dist/es/main.mjs +20 -0
- package/dist/es/main.mjs.map +1 -0
- package/dist/es/main10.mjs +33 -0
- package/dist/es/main10.mjs.map +1 -0
- package/dist/es/main11.mjs +12 -0
- package/dist/es/main11.mjs.map +1 -0
- package/dist/es/main12.mjs +14 -0
- package/dist/es/main12.mjs.map +1 -0
- package/dist/es/main13.mjs +12 -0
- package/dist/es/main13.mjs.map +1 -0
- package/dist/es/main14.mjs +5 -0
- package/dist/es/main14.mjs.map +1 -0
- package/dist/es/main15.mjs +12 -0
- package/dist/es/main15.mjs.map +1 -0
- package/dist/es/main16.mjs +25 -0
- package/dist/es/main16.mjs.map +1 -0
- package/dist/es/main17.mjs +54 -0
- package/dist/es/main17.mjs.map +1 -0
- package/dist/es/main18.mjs +88 -0
- package/dist/es/main18.mjs.map +1 -0
- package/dist/es/main19.mjs +18 -0
- package/dist/es/main19.mjs.map +1 -0
- package/dist/es/main2.mjs +9 -0
- package/dist/es/main2.mjs.map +1 -0
- package/dist/es/main20.mjs +21 -0
- package/dist/es/main20.mjs.map +1 -0
- package/dist/es/main21.mjs +61 -0
- package/dist/es/main21.mjs.map +1 -0
- package/dist/es/main3.mjs +46 -0
- package/dist/es/main3.mjs.map +1 -0
- package/dist/es/main4.mjs +23 -0
- package/dist/es/main4.mjs.map +1 -0
- package/dist/es/main5.mjs +69 -0
- package/dist/es/main5.mjs.map +1 -0
- package/dist/es/main6.mjs +35 -0
- package/dist/es/main6.mjs.map +1 -0
- package/dist/es/main7.mjs +65 -0
- package/dist/es/main7.mjs.map +1 -0
- package/dist/es/main8.mjs +14 -0
- package/dist/es/main8.mjs.map +1 -0
- package/dist/es/main9.mjs +26 -0
- package/dist/es/main9.mjs.map +1 -0
- package/dist/maplibre/cjs/main.js +520 -0
- package/dist/maplibre/es/main.mjs +20 -0
- package/dist/maplibre/es/main.mjs.map +1 -0
- package/dist/maplibre/es/main10.mjs +33 -0
- package/dist/maplibre/es/main10.mjs.map +1 -0
- package/dist/maplibre/es/main11.mjs +12 -0
- package/dist/maplibre/es/main11.mjs.map +1 -0
- package/dist/maplibre/es/main12.mjs +14 -0
- package/dist/maplibre/es/main12.mjs.map +1 -0
- package/dist/maplibre/es/main13.mjs +12 -0
- package/dist/maplibre/es/main13.mjs.map +1 -0
- package/dist/maplibre/es/main14.mjs +5 -0
- package/dist/maplibre/es/main14.mjs.map +1 -0
- package/dist/maplibre/es/main15.mjs +12 -0
- package/dist/maplibre/es/main15.mjs.map +1 -0
- package/dist/maplibre/es/main16.mjs +25 -0
- package/dist/maplibre/es/main16.mjs.map +1 -0
- package/dist/maplibre/es/main17.mjs +54 -0
- package/dist/maplibre/es/main17.mjs.map +1 -0
- package/dist/maplibre/es/main18.mjs +88 -0
- package/dist/maplibre/es/main18.mjs.map +1 -0
- package/dist/maplibre/es/main19.mjs +18 -0
- package/dist/maplibre/es/main19.mjs.map +1 -0
- package/dist/maplibre/es/main2.mjs +9 -0
- package/dist/maplibre/es/main2.mjs.map +1 -0
- package/dist/maplibre/es/main20.mjs +61 -0
- package/dist/maplibre/es/main20.mjs.map +1 -0
- package/dist/maplibre/es/main21.mjs +21 -0
- package/dist/maplibre/es/main21.mjs.map +1 -0
- package/dist/maplibre/es/main3.mjs +46 -0
- package/dist/maplibre/es/main3.mjs.map +1 -0
- package/dist/maplibre/es/main4.mjs +23 -0
- package/dist/maplibre/es/main4.mjs.map +1 -0
- package/dist/maplibre/es/main5.mjs +69 -0
- package/dist/maplibre/es/main5.mjs.map +1 -0
- package/dist/maplibre/es/main6.mjs +35 -0
- package/dist/maplibre/es/main6.mjs.map +1 -0
- package/dist/maplibre/es/main7.mjs +65 -0
- package/dist/maplibre/es/main7.mjs.map +1 -0
- package/dist/maplibre/es/main8.mjs +14 -0
- package/dist/maplibre/es/main8.mjs.map +1 -0
- package/dist/maplibre/es/main9.mjs +26 -0
- package/dist/maplibre/es/main9.mjs.map +1 -0
- package/dist/maplibre/types/api/canvas-props.d.ts +9 -0
- package/dist/maplibre/types/api/coordinates.d.ts +13 -0
- package/dist/maplibre/types/api/coords-to-vector-3.d.ts +3 -0
- package/dist/maplibre/types/api/coords.d.ts +5 -0
- package/dist/maplibre/types/api/index.d.ts +7 -0
- package/dist/maplibre/types/api/near-coordinates.d.ts +13 -0
- package/dist/maplibre/types/api/use-map.d.ts +3 -0
- package/dist/maplibre/types/api/vector-3-to-coords.d.ts +2 -0
- package/dist/maplibre/types/core/canvas-in-layer/use-canvas-in-layer.d.ts +15 -0
- package/dist/maplibre/types/core/canvas-in-layer/use-render.d.ts +15 -0
- package/dist/maplibre/types/core/canvas-in-layer/use-root.d.ts +11 -0
- package/dist/maplibre/types/core/canvas-overlay/canvas-portal.d.ts +10 -0
- package/dist/maplibre/types/core/canvas-overlay/init-canvas-fc.d.ts +11 -0
- package/dist/maplibre/types/core/canvas-overlay/render.d.ts +1 -0
- package/dist/maplibre/types/core/canvas-overlay/sync-camera-fc.d.ts +12 -0
- package/dist/maplibre/types/core/coords-to-matrix.d.ts +9 -0
- package/dist/maplibre/types/core/earth-radius.d.ts +1 -0
- package/dist/maplibre/types/core/generic-map.d.ts +49 -0
- package/dist/maplibre/types/core/matrix-utils.d.ts +7 -0
- package/dist/maplibre/types/core/sync-camera.d.ts +7 -0
- package/dist/maplibre/types/core/use-babylon-map.d.ts +32 -0
- package/dist/maplibre/types/core/use-coords-to-matrix.d.ts +6 -0
- package/dist/maplibre/types/core/use-coords.d.ts +5 -0
- package/dist/maplibre/types/core/use-function.d.ts +1 -0
- package/dist/maplibre/types/maplibre/canvas.d.ts +4 -0
- package/dist/maplibre/types/maplibre.index.d.ts +4 -0
- package/dist/types/api/canvas-props.d.ts +9 -0
- package/dist/types/api/coordinates.d.ts +13 -0
- package/dist/types/api/coords-to-vector-3.d.ts +3 -0
- package/dist/types/api/coords.d.ts +5 -0
- package/dist/types/api/index.d.ts +7 -0
- package/dist/types/api/near-coordinates.d.ts +13 -0
- package/dist/types/api/use-map.d.ts +3 -0
- package/dist/types/api/vector-3-to-coords.d.ts +2 -0
- package/dist/types/core/canvas-in-layer/use-canvas-in-layer.d.ts +15 -0
- package/dist/types/core/canvas-in-layer/use-render.d.ts +15 -0
- package/dist/types/core/canvas-in-layer/use-root.d.ts +11 -0
- package/dist/types/core/canvas-overlay/canvas-portal.d.ts +10 -0
- package/dist/types/core/canvas-overlay/init-canvas-fc.d.ts +11 -0
- package/dist/types/core/canvas-overlay/render.d.ts +1 -0
- package/dist/types/core/canvas-overlay/sync-camera-fc.d.ts +12 -0
- package/dist/types/core/coords-to-matrix.d.ts +9 -0
- package/dist/types/core/earth-radius.d.ts +1 -0
- package/dist/types/core/generic-map.d.ts +49 -0
- package/dist/types/core/matrix-utils.d.ts +7 -0
- package/dist/types/core/sync-camera.d.ts +7 -0
- package/dist/types/core/use-babylon-map.d.ts +32 -0
- package/dist/types/core/use-coords-to-matrix.d.ts +6 -0
- package/dist/types/core/use-coords.d.ts +5 -0
- package/dist/types/core/use-function.d.ts +1 -0
- package/dist/types/mapbox/canvas.d.ts +4 -0
- package/dist/types/mapbox.index.d.ts +4 -0
- package/package.json +58 -0
- package/plan.md +719 -0
- package/src/api/canvas-props.ts +10 -0
- package/src/api/coordinates.tsx +83 -0
- package/src/api/coords-to-vector-3.ts +39 -0
- package/src/api/coords.tsx +6 -0
- package/src/api/index.ts +7 -0
- package/src/api/near-coordinates.tsx +87 -0
- package/src/api/use-map.ts +8 -0
- package/src/api/vector-3-to-coords.ts +13 -0
- package/src/core/canvas-in-layer/use-canvas-in-layer.tsx +27 -0
- package/src/core/canvas-in-layer/use-render.ts +43 -0
- package/src/core/canvas-in-layer/use-root.tsx +82 -0
- package/src/core/canvas-overlay/canvas-portal.tsx +98 -0
- package/src/core/canvas-overlay/init-canvas-fc.tsx +45 -0
- package/src/core/canvas-overlay/render.tsx +1 -0
- package/src/core/canvas-overlay/sync-camera-fc.tsx +83 -0
- package/src/core/coords-to-matrix.ts +21 -0
- package/src/core/earth-radius.ts +1 -0
- package/src/core/events.ts +55 -0
- package/src/core/generic-map.ts +59 -0
- package/src/core/map-engine.tsx +70 -0
- package/src/core/matrix-utils.ts +22 -0
- package/src/core/sync-camera.ts +29 -0
- package/src/core/use-babylon-map.ts +46 -0
- package/src/core/use-coords-to-matrix.ts +13 -0
- package/src/core/use-coords.tsx +22 -0
- package/src/core/use-function.ts +10 -0
- package/src/mapbox/canvas.tsx +59 -0
- package/src/mapbox.index.ts +7 -0
- package/src/maplibre/canvas.tsx +59 -0
- package/src/maplibre.index.ts +7 -0
- package/src/vite-env.d.ts +1 -0
- package/stories/.ladle/components.tsx +50 -0
- package/stories/.ladle/style.css +63 -0
- package/stories/package.json +31 -0
- package/stories/pnpm-lock.yaml +5450 -0
- package/stories/sandbox.config.json +3 -0
- package/stories/src/adaptive-dpr.tsx +34 -0
- package/stories/src/billboard.stories.tsx +111 -0
- package/stories/src/buildings-3d.stories.tsx +280 -0
- package/stories/src/canvas/mapbox.stories.tsx +113 -0
- package/stories/src/canvas/maplibre.stories.tsx +93 -0
- package/stories/src/comparison.stories.tsx +161 -0
- package/stories/src/extrude/chaillot.ts +8 -0
- package/stories/src/exude-coordinates.stories.tsx +139 -0
- package/stories/src/free-3d-buildings/get-buildings-data.ts +49 -0
- package/stories/src/html-on-top.stories.tsx +156 -0
- package/stories/src/ifc/ifc-to-babylon.ts +97 -0
- package/stories/src/ifc/ifc.main.ts +904 -0
- package/stories/src/ifc/ifc2bb.ts +343 -0
- package/stories/src/ifc/model.ifc +14155 -0
- package/stories/src/ifc.stories.tsx +276 -0
- package/stories/src/mapbox/story-mapbox.tsx +97 -0
- package/stories/src/maplibre/story-maplibre.tsx +36 -0
- package/stories/src/multi-coordinates.stories.tsx +115 -0
- package/stories/src/pivot-controls.stories.tsx +148 -0
- package/stories/src/postprocessing.stories.tsx +125 -0
- package/stories/src/render-on-demand.stories.tsx +76 -0
- package/stories/src/story-map.tsx +44 -0
- package/stories/src/sunlight.stories.tsx +215 -0
- package/stories/src/vite-env.d.ts +1 -0
- package/stories/tsconfig.json +32 -0
- package/stories/tsconfig.node.json +10 -0
- package/stories/vite.config.ts +27 -0
- package/tsconfig.json +31 -0
- package/tsconfig.mapbox.json +7 -0
- package/tsconfig.maplibre.json +7 -0
- package/tsconfig.node.json +10 -0
- package/tsconfig.types.json +25 -0
- package/vite.config.ts +65 -0
|
@@ -0,0 +1,343 @@
|
|
|
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
|
+
/**
|
|
6
|
+
* Converts @ifc-lite/geometry MeshData into Babylon.js objects.
|
|
7
|
+
*
|
|
8
|
+
* Three rendering strategies are provided:
|
|
9
|
+
*
|
|
10
|
+
* meshDataToBabylon — one Mesh per entity (simple, good for picking)
|
|
11
|
+
* geometryResultToBatched — merge by color (fewer draw calls, moderate)
|
|
12
|
+
* batchWithVertexColors — merge ALL opaque into one draw call via vertex
|
|
13
|
+
* colors; transparent grouped by alpha (best perf).
|
|
14
|
+
* Returns a triangleMaps index for entity picking.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
Mesh,
|
|
19
|
+
VertexData,
|
|
20
|
+
StandardMaterial,
|
|
21
|
+
TransformNode,
|
|
22
|
+
Color3,
|
|
23
|
+
Vector3,
|
|
24
|
+
} from '@babylonjs/core';
|
|
25
|
+
import type { Scene } from '@babylonjs/core';
|
|
26
|
+
import type { MeshData, GeometryResult } from '@ifc-lite/geometry';
|
|
27
|
+
|
|
28
|
+
/** Map from expressId → Babylon.js mesh, for picking / highlighting */
|
|
29
|
+
export type ExpressIdMap = Map<number, Mesh>;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Maps a contiguous triangle range within a merged mesh back to an expressId.
|
|
33
|
+
* `start` and `count` are in triangle units (i.e. faceId from scene.pick).
|
|
34
|
+
*/
|
|
35
|
+
export type TriangleRange = { expressId: number; start: number; count: number };
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Per-mesh triangle → entity lookup table produced by batchWithVertexColors.
|
|
39
|
+
* Keys are the actual Babylon.js Mesh objects added to the scene.
|
|
40
|
+
*/
|
|
41
|
+
export type TriangleMaps = Map<Mesh, TriangleRange[]>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Convert a single MeshData into a Babylon.js Mesh.
|
|
45
|
+
*/
|
|
46
|
+
export function meshDataToBabylon(meshData: MeshData, scene: Scene): Mesh {
|
|
47
|
+
const mesh = new Mesh(`entity-${meshData.expressId}`, scene);
|
|
48
|
+
|
|
49
|
+
const vertexData = new VertexData();
|
|
50
|
+
vertexData.positions = meshData.positions;
|
|
51
|
+
vertexData.normals = meshData.normals;
|
|
52
|
+
vertexData.indices = meshData.indices;
|
|
53
|
+
vertexData.applyToMesh(mesh);
|
|
54
|
+
|
|
55
|
+
const [r, g, b, a] = meshData.color;
|
|
56
|
+
const material = new StandardMaterial(`mat-${meshData.expressId}`, scene);
|
|
57
|
+
material.diffuseColor = new Color3(r, g, b);
|
|
58
|
+
material.specularColor = new Color3(0, 0, 0);
|
|
59
|
+
if (a < 1) {
|
|
60
|
+
material.alpha = a;
|
|
61
|
+
material.backFaceCulling = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
mesh.material = material;
|
|
65
|
+
mesh.metadata = { expressId: meshData.expressId, ifcType: meshData.ifcType };
|
|
66
|
+
|
|
67
|
+
return mesh;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert an entire GeometryResult into a Babylon.js TransformNode tree.
|
|
72
|
+
*
|
|
73
|
+
* Returns the root node and an expressId→Mesh map for picking.
|
|
74
|
+
*/
|
|
75
|
+
export function geometryResultToBatched(result: GeometryResult, scene: Scene): {
|
|
76
|
+
root: TransformNode;
|
|
77
|
+
expressIdMap: ExpressIdMap;
|
|
78
|
+
} {
|
|
79
|
+
const root = new TransformNode('model-root', scene);
|
|
80
|
+
const expressIdMap: ExpressIdMap = new Map();
|
|
81
|
+
|
|
82
|
+
const colorBuckets = new Map<string, MeshData[]>();
|
|
83
|
+
for (const mesh of result.meshes) {
|
|
84
|
+
const key = mesh.color.join(',');
|
|
85
|
+
let bucket = colorBuckets.get(key);
|
|
86
|
+
if (!bucket) {
|
|
87
|
+
bucket = [];
|
|
88
|
+
colorBuckets.set(key, bucket);
|
|
89
|
+
}
|
|
90
|
+
bucket.push(mesh);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let bucketIndex = 0;
|
|
94
|
+
for (const [, meshes] of colorBuckets) {
|
|
95
|
+
let totalPositions = 0;
|
|
96
|
+
let totalIndices = 0;
|
|
97
|
+
for (const m of meshes) {
|
|
98
|
+
totalPositions += m.positions.length;
|
|
99
|
+
totalIndices += m.indices.length;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const positions = new Float32Array(totalPositions);
|
|
103
|
+
const normals = new Float32Array(totalPositions);
|
|
104
|
+
const indices = new Uint32Array(totalIndices);
|
|
105
|
+
|
|
106
|
+
let posOffset = 0;
|
|
107
|
+
let idxOffset = 0;
|
|
108
|
+
let vertexOffset = 0;
|
|
109
|
+
|
|
110
|
+
for (const m of meshes) {
|
|
111
|
+
positions.set(m.positions, posOffset);
|
|
112
|
+
normals.set(m.normals, posOffset);
|
|
113
|
+
|
|
114
|
+
for (let i = 0; i < m.indices.length; i++) {
|
|
115
|
+
indices[idxOffset + i] = m.indices[i] + vertexOffset;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
posOffset += m.positions.length;
|
|
119
|
+
idxOffset += m.indices.length;
|
|
120
|
+
vertexOffset += m.positions.length / 3;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const batchedMesh = new Mesh(`batch-${bucketIndex++}`, scene);
|
|
124
|
+
const vertexData = new VertexData();
|
|
125
|
+
vertexData.positions = positions;
|
|
126
|
+
vertexData.normals = normals;
|
|
127
|
+
vertexData.indices = indices;
|
|
128
|
+
vertexData.applyToMesh(batchedMesh);
|
|
129
|
+
|
|
130
|
+
const [r, g, b, a] = meshes[0].color;
|
|
131
|
+
const material = new StandardMaterial(`batch-mat-${bucketIndex}`, scene);
|
|
132
|
+
material.diffuseColor = new Color3(r, g, b);
|
|
133
|
+
material.specularColor = new Color3(0, 0, 0);
|
|
134
|
+
if (a < 1) {
|
|
135
|
+
material.alpha = a;
|
|
136
|
+
material.backFaceCulling = false;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
batchedMesh.material = material;
|
|
140
|
+
batchedMesh.parent = root;
|
|
141
|
+
|
|
142
|
+
for (const m of meshes) {
|
|
143
|
+
expressIdMap.set(m.expressId, batchedMesh);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { root, expressIdMap };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Highest-performance batching strategy for large models.
|
|
152
|
+
*
|
|
153
|
+
* Merges ALL opaque meshes into a single draw call using a vertex color
|
|
154
|
+
* attribute. Transparent meshes are grouped by alpha value. Alongside the
|
|
155
|
+
* root node, returns a `triangleMaps` index so individual entities can be
|
|
156
|
+
* identified by their Babylon.js scene.pick faceId after the fact — enabling
|
|
157
|
+
* object picking without a separate per-entity geometry layer.
|
|
158
|
+
*/
|
|
159
|
+
export function batchWithVertexColors(meshes: MeshData[], scene: Scene): {
|
|
160
|
+
root: TransformNode;
|
|
161
|
+
expressIdMap: ExpressIdMap;
|
|
162
|
+
triangleMaps: TriangleMaps;
|
|
163
|
+
} {
|
|
164
|
+
const root = new TransformNode('batch-root', scene);
|
|
165
|
+
const expressIdMap: ExpressIdMap = new Map();
|
|
166
|
+
const triangleMaps: TriangleMaps = new Map();
|
|
167
|
+
|
|
168
|
+
const opaque = meshes.filter((m) => m.color[3] >= 1);
|
|
169
|
+
const transparent = meshes.filter((m) => m.color[3] < 1);
|
|
170
|
+
|
|
171
|
+
if (opaque.length > 0) {
|
|
172
|
+
const { mesh, triangleRanges } = mergeWithVertexColors(opaque, scene, false);
|
|
173
|
+
mesh.parent = root;
|
|
174
|
+
triangleMaps.set(mesh, triangleRanges);
|
|
175
|
+
for (const m of opaque) expressIdMap.set(m.expressId, mesh);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (transparent.length > 0) {
|
|
179
|
+
const alphaGroups = new Map<number, MeshData[]>();
|
|
180
|
+
for (const m of transparent) {
|
|
181
|
+
const alpha = Math.round(m.color[3] * 100) / 100;
|
|
182
|
+
let bucket = alphaGroups.get(alpha);
|
|
183
|
+
if (!bucket) {
|
|
184
|
+
bucket = [];
|
|
185
|
+
alphaGroups.set(alpha, bucket);
|
|
186
|
+
}
|
|
187
|
+
bucket.push(m);
|
|
188
|
+
}
|
|
189
|
+
for (const [alpha, group] of alphaGroups) {
|
|
190
|
+
const { mesh, triangleRanges } = mergeWithVertexColors(group, scene, true, alpha);
|
|
191
|
+
mesh.parent = root;
|
|
192
|
+
triangleMaps.set(mesh, triangleRanges);
|
|
193
|
+
for (const m of group) expressIdMap.set(m.expressId, mesh);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { root, expressIdMap, triangleMaps };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Find the expressId for the entity whose triangles contain `faceId`.
|
|
202
|
+
* Uses binary search — O(log n) per pick operation.
|
|
203
|
+
* Returns null if not found.
|
|
204
|
+
*/
|
|
205
|
+
export function findEntityByFace(
|
|
206
|
+
ranges: TriangleRange[],
|
|
207
|
+
faceId: number,
|
|
208
|
+
): number | null {
|
|
209
|
+
let lo = 0;
|
|
210
|
+
let hi = ranges.length - 1;
|
|
211
|
+
while (lo <= hi) {
|
|
212
|
+
const mid = (lo + hi) >>> 1;
|
|
213
|
+
const r = ranges[mid];
|
|
214
|
+
if (faceId < r.start) {
|
|
215
|
+
hi = mid - 1;
|
|
216
|
+
} else if (faceId >= r.start + r.count) {
|
|
217
|
+
lo = mid + 1;
|
|
218
|
+
} else {
|
|
219
|
+
return r.expressId;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Merge an array of MeshData into one Mesh with per-vertex RGBA colors. */
|
|
226
|
+
function mergeWithVertexColors(
|
|
227
|
+
meshes: MeshData[],
|
|
228
|
+
scene: Scene,
|
|
229
|
+
transparent: boolean,
|
|
230
|
+
opacity = 1,
|
|
231
|
+
): { mesh: Mesh; triangleRanges: TriangleRange[] } {
|
|
232
|
+
let totalVertices = 0;
|
|
233
|
+
let totalIndices = 0;
|
|
234
|
+
for (const m of meshes) {
|
|
235
|
+
totalVertices += m.positions.length / 3;
|
|
236
|
+
totalIndices += m.indices.length;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const positions = new Float32Array(totalVertices * 3);
|
|
240
|
+
const normals = new Float32Array(totalVertices * 3);
|
|
241
|
+
const colors = new Float32Array(totalVertices * 4); // RGBA for Babylon.js
|
|
242
|
+
const indices = new Uint32Array(totalIndices);
|
|
243
|
+
|
|
244
|
+
const triangleRanges: TriangleRange[] = [];
|
|
245
|
+
let vOffset = 0;
|
|
246
|
+
let iOffset = 0;
|
|
247
|
+
|
|
248
|
+
for (const m of meshes) {
|
|
249
|
+
const vertCount = m.positions.length / 3;
|
|
250
|
+
const triCount = m.indices.length / 3;
|
|
251
|
+
|
|
252
|
+
positions.set(m.positions, vOffset * 3);
|
|
253
|
+
normals.set(m.normals, vOffset * 3);
|
|
254
|
+
|
|
255
|
+
const [r, g, b] = m.color;
|
|
256
|
+
for (let v = 0; v < vertCount; v++) {
|
|
257
|
+
colors[(vOffset + v) * 4 + 0] = r;
|
|
258
|
+
colors[(vOffset + v) * 4 + 1] = g;
|
|
259
|
+
colors[(vOffset + v) * 4 + 2] = b;
|
|
260
|
+
colors[(vOffset + v) * 4 + 3] = 1;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (let i = 0; i < m.indices.length; i++) {
|
|
264
|
+
indices[iOffset + i] = m.indices[i] + vOffset;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
triangleRanges.push({ expressId: m.expressId, start: iOffset / 3, count: triCount });
|
|
268
|
+
|
|
269
|
+
vOffset += vertCount;
|
|
270
|
+
iOffset += m.indices.length;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const mesh = new Mesh('batched', scene);
|
|
274
|
+
const vertexData = new VertexData();
|
|
275
|
+
vertexData.positions = positions;
|
|
276
|
+
vertexData.normals = normals;
|
|
277
|
+
vertexData.colors = colors;
|
|
278
|
+
vertexData.indices = indices;
|
|
279
|
+
vertexData.applyToMesh(mesh);
|
|
280
|
+
|
|
281
|
+
// White diffuse so vertex colors pass through as the actual surface color.
|
|
282
|
+
// Black specular — no per-fragment specular highlights on large merged meshes.
|
|
283
|
+
const material = new StandardMaterial('batched-mat', scene);
|
|
284
|
+
material.diffuseColor = new Color3(1, 1, 1);
|
|
285
|
+
material.specularColor = new Color3(0, 0, 0);
|
|
286
|
+
|
|
287
|
+
if (transparent) {
|
|
288
|
+
material.alpha = opacity;
|
|
289
|
+
material.backFaceCulling = false;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
mesh.material = material;
|
|
293
|
+
mesh.hasVertexAlpha = false; // vertex colors are RGB only, alpha via material
|
|
294
|
+
|
|
295
|
+
return { mesh, triangleRanges };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Dispose a TransformNode and all child meshes, releasing GPU resources.
|
|
300
|
+
*/
|
|
301
|
+
export function disposeNode(node: TransformNode) {
|
|
302
|
+
const meshes = node.getChildMeshes();
|
|
303
|
+
for (const mesh of meshes) {
|
|
304
|
+
if (mesh.material) mesh.material.dispose();
|
|
305
|
+
mesh.dispose();
|
|
306
|
+
}
|
|
307
|
+
node.dispose();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Compute the axis-aligned bounding box of all meshes under a node.
|
|
312
|
+
*/
|
|
313
|
+
export function computeBounds(root: TransformNode): {
|
|
314
|
+
center: Vector3;
|
|
315
|
+
size: Vector3;
|
|
316
|
+
maxDim: number;
|
|
317
|
+
} {
|
|
318
|
+
let minX = Infinity, minY = Infinity, minZ = Infinity;
|
|
319
|
+
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
|
|
320
|
+
|
|
321
|
+
for (const mesh of root.getChildMeshes()) {
|
|
322
|
+
mesh.computeWorldMatrix(true);
|
|
323
|
+
const bounds = mesh.getBoundingInfo().boundingBox;
|
|
324
|
+
const bMin = bounds.minimumWorld;
|
|
325
|
+
const bMax = bounds.maximumWorld;
|
|
326
|
+
if (bMin.x < minX) minX = bMin.x;
|
|
327
|
+
if (bMin.y < minY) minY = bMin.y;
|
|
328
|
+
if (bMin.z < minZ) minZ = bMin.z;
|
|
329
|
+
if (bMax.x > maxX) maxX = bMax.x;
|
|
330
|
+
if (bMax.y > maxY) maxY = bMax.y;
|
|
331
|
+
if (bMax.z > maxZ) maxZ = bMax.z;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const size = new Vector3(maxX - minX, maxY - minY, maxZ - minZ);
|
|
335
|
+
const center = new Vector3(
|
|
336
|
+
(minX + maxX) / 2,
|
|
337
|
+
(minY + maxY) / 2,
|
|
338
|
+
(minZ + maxZ) / 2,
|
|
339
|
+
);
|
|
340
|
+
const maxDim = Math.max(size.x, size.y, size.z);
|
|
341
|
+
|
|
342
|
+
return { center, size, maxDim };
|
|
343
|
+
}
|