@expofp/renderer 3.1.6 → 3.2.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/dist/index.d.ts +48 -1
- package/dist/index.js +781 -280
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { AlwaysDepth,
|
|
1
|
+
import { AlwaysDepth, BatchedMesh, Box3, BufferAttribute, BufferGeometry, Camera, Clock, Color, CustomBlending, DataTexture, DirectionalLight, DoubleSide, DynamicDrawUsage, FloatType, Frustum, Group, HemisphereLight, IntType, LessDepth, LessEqualDepth, LinearSRGBColorSpace, MathUtils, Matrix3, Matrix4, Mesh, MeshBasicMaterial, MeshPhysicalMaterial, MeshStandardMaterial, OneFactor, PMREMGenerator, PerspectiveCamera, Plane, PlaneGeometry, Quaternion, RGBAFormat, RGBAIntegerFormat, RGFormat, RGIntegerFormat, Raycaster, RedFormat, RedIntegerFormat, SRGBColorSpace, Scene, Sphere, Spherical, StreamDrawUsage, Texture, UnsignedIntType, Vector2, Vector3, Vector4, WebGLRenderer } from "three";
|
|
2
2
|
import { traverseAncestorsGenerator } from "three/examples/jsm/utils/SceneUtils.js";
|
|
3
3
|
import createLog from "debug";
|
|
4
4
|
import { BatchedText, Text } from "troika-three-text";
|
|
5
|
-
import { LineMaterial, LineSegmentsGeometry } from "three/examples/jsm/Addons.js";
|
|
5
|
+
import { BufferGeometryUtils, ColorEnvironment, LineMaterial, LineSegmentsGeometry } from "three/examples/jsm/Addons.js";
|
|
6
6
|
import { MaxRectsPacker, Rectangle } from "maxrects-packer";
|
|
7
7
|
import { colord, extend } from "colord";
|
|
8
8
|
import namesPlugin from "colord/plugins/names";
|
|
9
|
+
import { MeshoptDecoder } from "three/addons/libs/meshopt_decoder.module.js";
|
|
10
|
+
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
|
|
11
|
+
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
|
|
12
|
+
import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
|
|
9
13
|
import { DEG2RAD, MathUtils as MathUtils$1, RAD2DEG } from "three/src/math/MathUtils.js";
|
|
10
14
|
import { EventManager, Pan, Rotate } from "mjolnir.js";
|
|
11
15
|
//#region src/util/logging.ts
|
|
@@ -94,7 +98,7 @@ function getSquareTextureSize(capacity, pixelsPerInstance) {
|
|
|
94
98
|
*/
|
|
95
99
|
function getSquareTextureInfo(arrayType, channels, pixelsPerInstance, capacity) {
|
|
96
100
|
if (channels === 3) {
|
|
97
|
-
logger$
|
|
101
|
+
logger$16.debug("\"channels\" cannot be 3. Set to 4. More info: https://github.com/mrdoob/three.js/pull/23228");
|
|
98
102
|
channels = 4;
|
|
99
103
|
}
|
|
100
104
|
const size = getSquareTextureSize(capacity, pixelsPerInstance);
|
|
@@ -121,7 +125,7 @@ function getSquareTextureInfo(arrayType, channels, pixelsPerInstance, capacity)
|
|
|
121
125
|
format
|
|
122
126
|
};
|
|
123
127
|
}
|
|
124
|
-
var logger$
|
|
128
|
+
var logger$16 = createLogger("SquareDataTexture");
|
|
125
129
|
/**
|
|
126
130
|
* A class that extends `DataTexture` to manage a square texture optimized for instances rendering.
|
|
127
131
|
* It supports dynamic resizing, partial update based on rows, and allows setting/getting uniforms per instance.
|
|
@@ -163,7 +167,7 @@ var SquareDataTexture = class extends DataTexture {
|
|
|
163
167
|
setUniformAt(id, name, value) {
|
|
164
168
|
const schema = this.uniformMap.get(name);
|
|
165
169
|
if (!schema) {
|
|
166
|
-
logger$
|
|
170
|
+
logger$16.debug(`setUniformAt: uniform ${name} not found`);
|
|
167
171
|
return;
|
|
168
172
|
}
|
|
169
173
|
const { offset, size } = schema;
|
|
@@ -181,7 +185,7 @@ var SquareDataTexture = class extends DataTexture {
|
|
|
181
185
|
getUniformAt(id, name, target) {
|
|
182
186
|
const schema = this.uniformMap.get(name);
|
|
183
187
|
if (!schema) {
|
|
184
|
-
logger$
|
|
188
|
+
logger$16.debug(`getUniformAt: uniform ${name} not found`);
|
|
185
189
|
return 0;
|
|
186
190
|
}
|
|
187
191
|
const { offset, size } = schema;
|
|
@@ -300,7 +304,7 @@ var componentsArray = [
|
|
|
300
304
|
//#endregion
|
|
301
305
|
//#region src/batch/BatchedMesh.ts
|
|
302
306
|
var batchIdName = "batchId";
|
|
303
|
-
var logger$
|
|
307
|
+
var logger$15 = createLogger("BatchedMesh");
|
|
304
308
|
/**
|
|
305
309
|
* Class extending {@link ThreeBatchedMesh} to support per instance uniforms and disable WebGL multi draw
|
|
306
310
|
*/
|
|
@@ -323,7 +327,6 @@ var BatchedMesh$1 = class BatchedMesh$1 extends BatchedMesh {
|
|
|
323
327
|
*/
|
|
324
328
|
constructor(instanceCount, vertexCount, indexCount, material) {
|
|
325
329
|
super(instanceCount, vertexCount, indexCount, material);
|
|
326
|
-
material.forceSinglePass = true;
|
|
327
330
|
addDim(this);
|
|
328
331
|
if (!BatchedMesh$1.useMultiDraw) this.addEventListener("added", () => {
|
|
329
332
|
if (this.geometry.index !== null) {
|
|
@@ -376,7 +379,7 @@ var BatchedMesh$1 = class BatchedMesh$1 extends BatchedMesh {
|
|
|
376
379
|
if (BatchedMesh$1.useMultiDraw) return;
|
|
377
380
|
const batchCount = this.batchCount;
|
|
378
381
|
const gl = renderer.getContext();
|
|
379
|
-
if (geometry.index == null) return logger$
|
|
382
|
+
if (geometry.index == null) return logger$15.debug("No index buffer", this.parent?.name);
|
|
380
383
|
const type = this.getIndexType(gl, geometry.index);
|
|
381
384
|
gl.drawElements(gl.TRIANGLES, batchCount, type, 0);
|
|
382
385
|
renderer.info.update(batchCount, gl.TRIANGLES, 1);
|
|
@@ -391,7 +394,7 @@ var BatchedMesh$1 = class BatchedMesh$1 extends BatchedMesh {
|
|
|
391
394
|
*/
|
|
392
395
|
getUniformAt(id, name, target) {
|
|
393
396
|
if (!this.uniformsTexture) {
|
|
394
|
-
logger$
|
|
397
|
+
logger$15.debug(`getUniformAt: uniforms texture not initialized`);
|
|
395
398
|
return 0;
|
|
396
399
|
}
|
|
397
400
|
return this.uniformsTexture.getUniformAt(id, name, target);
|
|
@@ -404,7 +407,7 @@ var BatchedMesh$1 = class BatchedMesh$1 extends BatchedMesh {
|
|
|
404
407
|
*/
|
|
405
408
|
setUniformAt(instanceId, name, value) {
|
|
406
409
|
if (!this.uniformsTexture) {
|
|
407
|
-
logger$
|
|
410
|
+
logger$15.debug(`setUniformAt: uniforms texture not initialized`);
|
|
408
411
|
return;
|
|
409
412
|
}
|
|
410
413
|
this.uniformsTexture.setUniformAt(instanceId, name, value);
|
|
@@ -727,6 +730,17 @@ var BatchedText$1 = class extends BatchedText {
|
|
|
727
730
|
if (pendingSyncs.length) Promise.all(pendingSyncs).then(repack);
|
|
728
731
|
else repack();
|
|
729
732
|
}
|
|
733
|
+
createDerivedMaterial(baseMaterial) {
|
|
734
|
+
const derivedMaterial = super.createDerivedMaterial(baseMaterial);
|
|
735
|
+
derivedMaterial.onBeforeCompile = (shader) => {
|
|
736
|
+
shader.vertexShader = shader.vertexShader.replace("void main() {", `
|
|
737
|
+
void main () {
|
|
738
|
+
// Uninitialized vec2 causes problems on Samsung Android devices
|
|
739
|
+
uTroikaPositionOffset = vec2(0., 0.);
|
|
740
|
+
`);
|
|
741
|
+
};
|
|
742
|
+
return derivedMaterial;
|
|
743
|
+
}
|
|
730
744
|
repackBatchedGeometry() {
|
|
731
745
|
const geometry = this.geometry;
|
|
732
746
|
const batchedAttributes = geometry.attributes;
|
|
@@ -1067,7 +1081,6 @@ var dimColorFrag = `
|
|
|
1067
1081
|
var POLYGON_OFFSET_MULTIPLIER = 12;
|
|
1068
1082
|
var sharedParameters = {
|
|
1069
1083
|
side: DoubleSide,
|
|
1070
|
-
transparent: true,
|
|
1071
1084
|
forceSinglePass: true,
|
|
1072
1085
|
depthFunc: AlwaysDepth
|
|
1073
1086
|
};
|
|
@@ -1075,6 +1088,7 @@ var sharedParameters = {
|
|
|
1075
1088
|
var MaterialSystem = class {
|
|
1076
1089
|
backgroundMaterial;
|
|
1077
1090
|
viewport = new Vector4();
|
|
1091
|
+
lightingMaterials = [];
|
|
1078
1092
|
/**
|
|
1079
1093
|
* Creates a line material.
|
|
1080
1094
|
* @param params {@link LineMaterialParameters}
|
|
@@ -1103,8 +1117,7 @@ var MaterialSystem = class {
|
|
|
1103
1117
|
uniforms["resolution"].value.set(this.viewport.z, this.viewport.w);
|
|
1104
1118
|
}
|
|
1105
1119
|
};
|
|
1106
|
-
|
|
1107
|
-
this.addPolygonOffset(material);
|
|
1120
|
+
this.patchMaterial(material);
|
|
1108
1121
|
return material;
|
|
1109
1122
|
}
|
|
1110
1123
|
/**
|
|
@@ -1113,14 +1126,21 @@ var MaterialSystem = class {
|
|
|
1113
1126
|
* @returns MeshBasicMaterial instance
|
|
1114
1127
|
*/
|
|
1115
1128
|
createColorMaterial(params = {}) {
|
|
1116
|
-
const
|
|
1129
|
+
const materialConstructor = params.lightingEnable ? MeshPhysicalMaterial : MeshBasicMaterial;
|
|
1130
|
+
const color = params.color ?? 16777215;
|
|
1131
|
+
const opacity = params.opacity ?? 1;
|
|
1132
|
+
const transparent = opacity < 1;
|
|
1133
|
+
const material = new materialConstructor({
|
|
1117
1134
|
...sharedParameters,
|
|
1118
|
-
color
|
|
1119
|
-
opacity
|
|
1135
|
+
color,
|
|
1136
|
+
opacity,
|
|
1137
|
+
transparent
|
|
1120
1138
|
});
|
|
1121
|
-
if (
|
|
1122
|
-
|
|
1123
|
-
|
|
1139
|
+
if (transparent) {
|
|
1140
|
+
material.depthWrite = false;
|
|
1141
|
+
material.depthFunc = LessDepth;
|
|
1142
|
+
} else if (params.depthEnable) material.depthFunc = LessEqualDepth;
|
|
1143
|
+
this.patchMaterial(material, { lightingEnable: params.lightingEnable });
|
|
1124
1144
|
return material;
|
|
1125
1145
|
}
|
|
1126
1146
|
/**
|
|
@@ -1131,18 +1151,37 @@ var MaterialSystem = class {
|
|
|
1131
1151
|
createTextureMaterial(params) {
|
|
1132
1152
|
const material = new MeshBasicMaterial({
|
|
1133
1153
|
...sharedParameters,
|
|
1134
|
-
map: params.map
|
|
1154
|
+
map: params.map,
|
|
1155
|
+
transparent: true,
|
|
1156
|
+
depthFunc: LessDepth
|
|
1135
1157
|
});
|
|
1136
|
-
if (params.depthEnable) material.depthFunc = LessEqualDepth;
|
|
1137
1158
|
if (params.uvOffset) material.onBeforeCompile = (shader) => {
|
|
1138
1159
|
shader.vertexShader = shader.vertexShader.replace("#include <uv_vertex>", `
|
|
1139
1160
|
#include <uv_vertex>
|
|
1140
1161
|
vMapUv = uv * uvOffset.zw + uvOffset.xy;
|
|
1141
1162
|
`);
|
|
1142
1163
|
};
|
|
1164
|
+
this.patchMaterial(material);
|
|
1165
|
+
return material;
|
|
1166
|
+
}
|
|
1167
|
+
/**
|
|
1168
|
+
* Apply the engine's cross-cutting material patches: dim shader hooks, polygon offset,
|
|
1169
|
+
* and (optional) registration with the lighting system. Used internally by every
|
|
1170
|
+
* factory in this class and exposed publicly so externally-built materials (e.g. PBR
|
|
1171
|
+
* materials cloned from GLTF source) can opt in to the same behavior.
|
|
1172
|
+
* @param material material to patch
|
|
1173
|
+
* @param opts patch options
|
|
1174
|
+
* @param opts.lightingEnable when true and the material is a MeshPhysicalMaterial,
|
|
1175
|
+
* configures it for the project's lighting pipeline and registers it
|
|
1176
|
+
*/
|
|
1177
|
+
patchMaterial(material, opts = {}) {
|
|
1143
1178
|
addDimToMaterial(material);
|
|
1144
1179
|
this.addPolygonOffset(material);
|
|
1145
|
-
|
|
1180
|
+
if (opts.lightingEnable && material instanceof MeshPhysicalMaterial) {
|
|
1181
|
+
material.ior = 1;
|
|
1182
|
+
material.specularIntensity = 0;
|
|
1183
|
+
this.lightingMaterials.push(material);
|
|
1184
|
+
}
|
|
1146
1185
|
}
|
|
1147
1186
|
/**
|
|
1148
1187
|
* Creates a background material. Used for the background layer to support dimming the background.
|
|
@@ -1213,6 +1252,9 @@ function isTextDef(def) {
|
|
|
1213
1252
|
function isLineDef(def) {
|
|
1214
1253
|
return def.points !== void 0;
|
|
1215
1254
|
}
|
|
1255
|
+
function isModelDef(def) {
|
|
1256
|
+
return def.url !== void 0;
|
|
1257
|
+
}
|
|
1216
1258
|
function isLayerDef(def) {
|
|
1217
1259
|
return def.children !== void 0;
|
|
1218
1260
|
}
|
|
@@ -1228,6 +1270,9 @@ function isTextLayer(layer) {
|
|
|
1228
1270
|
function isLineLayer(layer) {
|
|
1229
1271
|
return layer.children[0] && isLineDef(layer.children[0]);
|
|
1230
1272
|
}
|
|
1273
|
+
function isModelLayer(layer) {
|
|
1274
|
+
return layer.children[0] && isModelDef(layer.children[0]);
|
|
1275
|
+
}
|
|
1231
1276
|
function isLayerLayer(layer) {
|
|
1232
1277
|
return layer.children[0] && isLayerDef(layer.children[0]);
|
|
1233
1278
|
}
|
|
@@ -1341,13 +1386,14 @@ var RenderableSystem = class {
|
|
|
1341
1386
|
* @param firstUpdate whether this is the first update for this def. Set to true when the def is first added to the scene.
|
|
1342
1387
|
*/
|
|
1343
1388
|
updateDef(def, firstUpdate = false) {
|
|
1344
|
-
const
|
|
1345
|
-
if (!
|
|
1346
|
-
const { object: mesh, instanceIds }
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1389
|
+
const mappings = this.getObjectInstanceByDef(def);
|
|
1390
|
+
if (!mappings) return;
|
|
1391
|
+
for (const { object: mesh, instanceIds } of mappings) {
|
|
1392
|
+
for (const instanceId of instanceIds) mesh.setVisibleAt(instanceId, !def.hidden);
|
|
1393
|
+
if (def.hidden && !firstUpdate) continue;
|
|
1394
|
+
for (const instanceId of instanceIds) toggleInstanceDim(mesh, instanceId, def.dim);
|
|
1395
|
+
this.updateDefImpl(def, mesh, instanceIds, firstUpdate);
|
|
1396
|
+
}
|
|
1351
1397
|
}
|
|
1352
1398
|
/**
|
|
1353
1399
|
* Update an existing collection with a new layer definition.
|
|
@@ -1418,7 +1464,8 @@ var RenderableSystem = class {
|
|
|
1418
1464
|
this.logger.debug(`Tried to register def with empty instanceIds %O`, def);
|
|
1419
1465
|
return;
|
|
1420
1466
|
}
|
|
1421
|
-
this.mapDefToObject.set(def,
|
|
1467
|
+
if (!this.mapDefToObject.has(def)) this.mapDefToObject.set(def, []);
|
|
1468
|
+
this.mapDefToObject.get(def).push({
|
|
1422
1469
|
object,
|
|
1423
1470
|
instanceIds: ids
|
|
1424
1471
|
});
|
|
@@ -1427,29 +1474,57 @@ var RenderableSystem = class {
|
|
|
1427
1474
|
this.updateDef(def, true);
|
|
1428
1475
|
}
|
|
1429
1476
|
/**
|
|
1430
|
-
*
|
|
1477
|
+
* Register a single instance mapping between a def and an object/instance, appending
|
|
1478
|
+
* one entry per call. Use when a single def maps to multiple instances across one or
|
|
1479
|
+
* more containers (e.g. a model spread across several BatchedMesh batches). Pushes
|
|
1480
|
+
* once per instance into the object→defs map so `getDefsByObject(obj)[batchId]` stays
|
|
1481
|
+
* aligned with sequential `BatchedMesh.addInstance` ids.
|
|
1482
|
+
* @param def def to register
|
|
1483
|
+
* @param object container the def's instance lives in
|
|
1484
|
+
* @param instanceId single instance id in the container
|
|
1485
|
+
*/
|
|
1486
|
+
registerDefInstance(def, object, instanceId) {
|
|
1487
|
+
if (!this.mapDefToObject.has(def)) this.mapDefToObject.set(def, []);
|
|
1488
|
+
this.mapDefToObject.get(def).push({
|
|
1489
|
+
object,
|
|
1490
|
+
instanceIds: [instanceId]
|
|
1491
|
+
});
|
|
1492
|
+
if (!this.mapObjectToDefs.has(object)) this.mapObjectToDefs.set(object, []);
|
|
1493
|
+
this.mapObjectToDefs.get(object).push(def);
|
|
1494
|
+
this.updateDef(def, true);
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Unregister a def and its associated object mappings.
|
|
1431
1498
|
* @param def def to unregister
|
|
1432
1499
|
*/
|
|
1433
1500
|
unregisterDef(def) {
|
|
1434
|
-
const
|
|
1435
|
-
if (
|
|
1436
|
-
const { object }
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1501
|
+
const mappings = this.mapDefToObject.get(def);
|
|
1502
|
+
if (mappings) {
|
|
1503
|
+
for (const { object } of mappings) {
|
|
1504
|
+
const defs = this.mapObjectToDefs.get(object);
|
|
1505
|
+
if (defs) {
|
|
1506
|
+
this.mapObjectToDefs.set(object, defs.filter((d) => d !== def));
|
|
1507
|
+
if (this.mapObjectToDefs.get(object).length === 0) this.mapObjectToDefs.delete(object);
|
|
1508
|
+
}
|
|
1441
1509
|
}
|
|
1442
1510
|
this.mapDefToObject.delete(def);
|
|
1443
1511
|
}
|
|
1444
1512
|
}
|
|
1445
1513
|
/**
|
|
1446
|
-
* Unregister all defs associated with an object.
|
|
1514
|
+
* Unregister all defs associated with an object. Only removes the entries that point
|
|
1515
|
+
* at this object — defs with mappings to other containers keep those entries intact.
|
|
1447
1516
|
* @param object object to unregister
|
|
1448
1517
|
*/
|
|
1449
1518
|
unregisterObject(object) {
|
|
1450
1519
|
const defs = this.mapObjectToDefs.get(object);
|
|
1451
1520
|
if (defs) {
|
|
1452
|
-
for (const def of defs)
|
|
1521
|
+
for (const def of defs) {
|
|
1522
|
+
const mappings = this.mapDefToObject.get(def);
|
|
1523
|
+
if (!mappings) continue;
|
|
1524
|
+
const remaining = mappings.filter((m) => m.object !== object);
|
|
1525
|
+
if (remaining.length === 0) this.mapDefToObject.delete(def);
|
|
1526
|
+
else this.mapDefToObject.set(def, remaining);
|
|
1527
|
+
}
|
|
1453
1528
|
this.mapObjectToDefs.delete(object);
|
|
1454
1529
|
}
|
|
1455
1530
|
}
|
|
@@ -1461,17 +1536,18 @@ var RenderableSystem = class {
|
|
|
1461
1536
|
this.mapObjectToDefs.clear();
|
|
1462
1537
|
}
|
|
1463
1538
|
/**
|
|
1464
|
-
* Lookup object/instance by def.
|
|
1539
|
+
* Lookup object/instance mappings by def. Returns an array because a single def can
|
|
1540
|
+
* map to multiple containers (e.g. a model spread across batches keyed by material).
|
|
1465
1541
|
* @param def def to lookup
|
|
1466
|
-
* @returns
|
|
1542
|
+
* @returns array of object/instance-id mappings, or undefined if none
|
|
1467
1543
|
*/
|
|
1468
1544
|
getObjectInstanceByDef(def) {
|
|
1469
|
-
const
|
|
1470
|
-
if (!
|
|
1545
|
+
const mappings = this.mapDefToObject.get(def);
|
|
1546
|
+
if (!mappings || mappings.length === 0) {
|
|
1471
1547
|
this.logger.debug(`No object mapping found for def %O`, def);
|
|
1472
1548
|
return;
|
|
1473
1549
|
}
|
|
1474
|
-
return
|
|
1550
|
+
return mappings;
|
|
1475
1551
|
}
|
|
1476
1552
|
/**
|
|
1477
1553
|
* Lookup defs by object.
|
|
@@ -1491,7 +1567,7 @@ var RenderableSystem = class {
|
|
|
1491
1567
|
};
|
|
1492
1568
|
//#endregion
|
|
1493
1569
|
//#region src/geometry/image.ts
|
|
1494
|
-
var logger$
|
|
1570
|
+
var logger$14 = createLogger("image");
|
|
1495
1571
|
/**
|
|
1496
1572
|
* A system that handles the rendering of image defs.
|
|
1497
1573
|
*/
|
|
@@ -1509,10 +1585,10 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1509
1585
|
* @param materialSystem {@link MaterialSystem}
|
|
1510
1586
|
*/
|
|
1511
1587
|
constructor(ctx, materialSystem) {
|
|
1512
|
-
super("image", ctx, logger$
|
|
1588
|
+
super("image", ctx, logger$14);
|
|
1513
1589
|
this.materialSystem = materialSystem;
|
|
1514
1590
|
const atlasTextureSize = ctx.three.capabilities.maxTextureSize;
|
|
1515
|
-
logger$
|
|
1591
|
+
logger$14.debug(`Max texture size: ${atlasTextureSize}`);
|
|
1516
1592
|
this.packer = new MaxRectsPacker(atlasTextureSize, atlasTextureSize, 1, { pot: false });
|
|
1517
1593
|
}
|
|
1518
1594
|
dispose() {
|
|
@@ -1583,7 +1659,7 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1583
1659
|
oldTexture?.dispose();
|
|
1584
1660
|
mesh.visible = true;
|
|
1585
1661
|
data.appliedScale = targetScale;
|
|
1586
|
-
logger$
|
|
1662
|
+
logger$14.debug(`Rendered atlas for ${mesh.name || mesh.parent?.name} at scale ${targetScale.toFixed(3)}`);
|
|
1587
1663
|
}
|
|
1588
1664
|
}
|
|
1589
1665
|
disposeObject(object) {
|
|
@@ -1603,7 +1679,7 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1603
1679
|
}
|
|
1604
1680
|
computeResizeFactor() {
|
|
1605
1681
|
if (!this.memoryLimitMb) {
|
|
1606
|
-
logger$
|
|
1682
|
+
logger$14.debug("Memory limit is not set, atlases will not be scaled");
|
|
1607
1683
|
return 1;
|
|
1608
1684
|
}
|
|
1609
1685
|
let totalResizable = 0;
|
|
@@ -1614,17 +1690,17 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1614
1690
|
else totalNonResizable += bytes;
|
|
1615
1691
|
}
|
|
1616
1692
|
if (totalResizable === 0) {
|
|
1617
|
-
logger$
|
|
1693
|
+
logger$14.debug("No resizable atlases, atlases will not be scaled");
|
|
1618
1694
|
return 1;
|
|
1619
1695
|
}
|
|
1620
1696
|
const budget = this.memoryLimitMb * 1024 * 1024 - totalNonResizable;
|
|
1621
1697
|
if (budget <= 0) {
|
|
1622
|
-
logger$
|
|
1698
|
+
logger$14.debug("Memory limit is too low, unable to resize textures.");
|
|
1623
1699
|
return 1;
|
|
1624
1700
|
}
|
|
1625
1701
|
const factor = Math.sqrt(budget / totalResizable);
|
|
1626
1702
|
if (factor >= 1) return 1;
|
|
1627
|
-
logger$
|
|
1703
|
+
logger$14.debug(`Resize factor: ${factor.toFixed(3)} (budget ${budget} bytes, resizable ${totalResizable} bytes)`);
|
|
1628
1704
|
return factor;
|
|
1629
1705
|
}
|
|
1630
1706
|
packImages(images) {
|
|
@@ -1646,8 +1722,8 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1646
1722
|
const boundsHeight = image.bounds.size.y;
|
|
1647
1723
|
const ratio = sourceArea / (boundsWidth * boundsHeight);
|
|
1648
1724
|
if (ratio > 1e3) {
|
|
1649
|
-
logger$
|
|
1650
|
-
logger$
|
|
1725
|
+
logger$14.debug(`Image bounds: ${boundsWidth}x${boundsHeight}`, `Image source: ${sourceWidth}x${sourceHeight}`);
|
|
1726
|
+
logger$14.warn(`Image bounds area is ${ratio.toFixed(2)} times smaller than the image.`);
|
|
1651
1727
|
}
|
|
1652
1728
|
const rect = new Rectangle(image.source.width, image.source.height);
|
|
1653
1729
|
rect.data = [imageWithIndex];
|
|
@@ -1656,7 +1732,7 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1656
1732
|
} else existingRect.data.push(imageWithIndex);
|
|
1657
1733
|
}
|
|
1658
1734
|
this.packer.addArray(rectangles);
|
|
1659
|
-
this.packer.bins.forEach((bin) => logger$
|
|
1735
|
+
this.packer.bins.forEach((bin) => logger$14.debug(`Bin: ${bin.width}x${bin.height}, ${bin.rects.length} rectangles`));
|
|
1660
1736
|
return this.packer.bins;
|
|
1661
1737
|
}
|
|
1662
1738
|
};
|
|
@@ -1668,7 +1744,7 @@ function createAtlas(bin, scale) {
|
|
|
1668
1744
|
const ctx = canvas.getContext("2d");
|
|
1669
1745
|
for (const rect of bin.rects) ctx.drawImage(rect.data[0].def.source, Math.floor(rect.x * scale), Math.floor(rect.y * scale), Math.floor(rect.width * scale), Math.floor(rect.height * scale));
|
|
1670
1746
|
const t1 = performance.now();
|
|
1671
|
-
logger$
|
|
1747
|
+
logger$14.debug(`Create atlas (${canvas.width}x${canvas.height}) took ${(t1 - t0).toFixed(2)} milliseconds.`);
|
|
1672
1748
|
return createTexture(canvas);
|
|
1673
1749
|
}
|
|
1674
1750
|
function createPlaceholderTexture() {
|
|
@@ -1687,7 +1763,7 @@ function getAtlasSizeBytes(width, height) {
|
|
|
1687
1763
|
}
|
|
1688
1764
|
//#endregion
|
|
1689
1765
|
//#region src/geometry/line.ts
|
|
1690
|
-
var logger$
|
|
1766
|
+
var logger$13 = createLogger("line");
|
|
1691
1767
|
/**
|
|
1692
1768
|
* A system that handles the rendering of line defs.
|
|
1693
1769
|
*/
|
|
@@ -1698,7 +1774,7 @@ var LineSystem = class extends RenderableSystem {
|
|
|
1698
1774
|
* @param materialSystem {@link MaterialSystem}
|
|
1699
1775
|
*/
|
|
1700
1776
|
constructor(ctx, materialSystem) {
|
|
1701
|
-
super("line", ctx, logger$
|
|
1777
|
+
super("line", ctx, logger$13);
|
|
1702
1778
|
this.materialSystem = materialSystem;
|
|
1703
1779
|
}
|
|
1704
1780
|
buildLayer(layer) {
|
|
@@ -2106,7 +2182,7 @@ function computeBoundingSphere(bounds, origin, out = new Sphere()) {
|
|
|
2106
2182
|
}
|
|
2107
2183
|
//#endregion
|
|
2108
2184
|
//#region src/geometry/mesh.ts
|
|
2109
|
-
var logger$
|
|
2185
|
+
var logger$12 = createLogger("mesh");
|
|
2110
2186
|
extend([namesPlugin]);
|
|
2111
2187
|
/**
|
|
2112
2188
|
* A system that handles the rendering of shape defs.
|
|
@@ -2125,7 +2201,7 @@ var MeshSystem = class extends RenderableSystem {
|
|
|
2125
2201
|
* @param materialSystem {@link MaterialSystem}
|
|
2126
2202
|
*/
|
|
2127
2203
|
constructor(ctx, materialSystem) {
|
|
2128
|
-
super("mesh", ctx, logger$
|
|
2204
|
+
super("mesh", ctx, logger$12);
|
|
2129
2205
|
this.materialSystem = materialSystem;
|
|
2130
2206
|
}
|
|
2131
2207
|
buildLayer(layer) {
|
|
@@ -2164,24 +2240,25 @@ var MeshSystem = class extends RenderableSystem {
|
|
|
2164
2240
|
const isPolygon = shape instanceof Polygon;
|
|
2165
2241
|
const isRect = shape instanceof Rect;
|
|
2166
2242
|
if (expectedShapeType === "polygon" && !isPolygon || expectedShapeType === "rect" && !isRect) {
|
|
2167
|
-
logger$
|
|
2243
|
+
logger$12.warn("Shape type changing not supported %O", shapeDef);
|
|
2168
2244
|
return;
|
|
2169
2245
|
}
|
|
2170
2246
|
if (isPolygon) {
|
|
2171
2247
|
const geometryRange = mesh.getGeometryRangeAt(geometryId);
|
|
2172
|
-
|
|
2173
|
-
|
|
2248
|
+
let newGeometry = this.buildPolygonGeometry(shape);
|
|
2249
|
+
if (mesh.userData["is3D"]) newGeometry = processGeometryFor3D(newGeometry);
|
|
2250
|
+
const { vertices, indices } = countGeometry(newGeometry);
|
|
2251
|
+
if (vertices != geometryRange?.reservedVertexCount || indices != Math.max(geometryRange.reservedIndexCount, 0)) {
|
|
2252
|
+
logger$12.warn("Polygon geometry changing not supported %O", shapeDef);
|
|
2174
2253
|
return;
|
|
2175
2254
|
}
|
|
2176
|
-
|
|
2177
|
-
if (mesh.geometry.hasAttribute("normal")) geometry.computeVertexNormals();
|
|
2178
|
-
mesh.setGeometryAt(geometryId, geometry);
|
|
2255
|
+
mesh.setGeometryAt(geometryId, newGeometry);
|
|
2179
2256
|
} else if (isRect) this.updateRect(shape, mesh, instanceId);
|
|
2180
2257
|
}
|
|
2181
2258
|
updateColor(shapeDef, mesh, instanceId) {
|
|
2182
2259
|
const color = this.normalizeColor(shapeDef.color);
|
|
2183
2260
|
if (!color) {
|
|
2184
|
-
logger$
|
|
2261
|
+
logger$12.warn(`Invalid color: ${shapeDef.color} %O`, shapeDef);
|
|
2185
2262
|
return;
|
|
2186
2263
|
}
|
|
2187
2264
|
mesh.setColorAt(instanceId, this.color.setRGB(color.r / 255, color.g / 255, color.b / 255, SRGBColorSpace));
|
|
@@ -2207,10 +2284,7 @@ var MeshSystem = class extends RenderableSystem {
|
|
|
2207
2284
|
({vertices, indices} = countGeometry(rectGeom));
|
|
2208
2285
|
} else if (shapeDef.shape instanceof Polygon) {
|
|
2209
2286
|
let geometry = this.buildPolygonGeometry(shapeDef.shape);
|
|
2210
|
-
if (is3D)
|
|
2211
|
-
geometry = geometry.toNonIndexed();
|
|
2212
|
-
geometry.computeVertexNormals();
|
|
2213
|
-
}
|
|
2287
|
+
if (is3D) geometry = processGeometryFor3D(geometry);
|
|
2214
2288
|
shapeDefToGeometry.set(shapeDef, geometry);
|
|
2215
2289
|
({vertices, indices} = countGeometry(geometry));
|
|
2216
2290
|
}
|
|
@@ -2224,7 +2298,8 @@ var MeshSystem = class extends RenderableSystem {
|
|
|
2224
2298
|
});
|
|
2225
2299
|
const batchedMesh = new BatchedMesh$1(shapes.length, vertexCount, indexCount, material);
|
|
2226
2300
|
const rectGeometryId = rectAdded ? batchedMesh.addGeometry(rectGeom) : void 0;
|
|
2227
|
-
batchedMesh.setCustomSort((list) => this.sortInstances(batchedMesh, list));
|
|
2301
|
+
batchedMesh.setCustomSort((list) => this.sortInstances(batchedMesh, is3D, opacity < 1, list));
|
|
2302
|
+
if (is3D) batchedMesh.userData["is3D"] = true;
|
|
2228
2303
|
for (const shapeDef of shapes) {
|
|
2229
2304
|
let instanceId;
|
|
2230
2305
|
let type;
|
|
@@ -2254,18 +2329,316 @@ var MeshSystem = class extends RenderableSystem {
|
|
|
2254
2329
|
buildPolygonGeometry(polygon) {
|
|
2255
2330
|
return new BufferGeometry().setFromPoints(polygon.vertices).setIndex(polygon.indices.flat());
|
|
2256
2331
|
}
|
|
2257
|
-
sortInstances(mesh, list) {
|
|
2332
|
+
sortInstances(mesh, is3D, isTransparent, list) {
|
|
2258
2333
|
const shapeDefs = this.getDefsByObject(mesh);
|
|
2259
2334
|
list.sort((a, b) => {
|
|
2260
2335
|
const aDef = shapeDefs[a.index];
|
|
2261
2336
|
const bDef = shapeDefs[b.index];
|
|
2262
|
-
|
|
2337
|
+
const aDim = aDef.dim === false ? 1 : 0;
|
|
2338
|
+
const bDim = bDef.dim === false ? 1 : 0;
|
|
2339
|
+
if (aDim !== bDim) return aDim - bDim;
|
|
2340
|
+
if (!is3D) return 0;
|
|
2341
|
+
return isTransparent ? sortTransparent(a, b) : sortOpaque(a, b);
|
|
2263
2342
|
});
|
|
2264
2343
|
}
|
|
2265
2344
|
};
|
|
2345
|
+
function sortOpaque(a, b) {
|
|
2346
|
+
return a.z - b.z;
|
|
2347
|
+
}
|
|
2348
|
+
function sortTransparent(a, b) {
|
|
2349
|
+
return b.z - a.z;
|
|
2350
|
+
}
|
|
2351
|
+
function processGeometryFor3D(geometry) {
|
|
2352
|
+
let processedGeometry = geometry;
|
|
2353
|
+
processedGeometry = processedGeometry.toNonIndexed();
|
|
2354
|
+
processedGeometry.computeVertexNormals();
|
|
2355
|
+
processedGeometry = BufferGeometryUtils.toCreasedNormals(processedGeometry, Math.PI / 6);
|
|
2356
|
+
return processedGeometry;
|
|
2357
|
+
}
|
|
2358
|
+
//#endregion
|
|
2359
|
+
//#region src/geometry/model.ts
|
|
2360
|
+
var logger$11 = createLogger("model");
|
|
2361
|
+
/**
|
|
2362
|
+
* Converts the GLB Y-up coordinate system to the engine's Z-up convention.
|
|
2363
|
+
* Matches the rotation/scale applied at the scene root in {@link import("../loaders/glb.ts")}.
|
|
2364
|
+
*/
|
|
2365
|
+
var Y_UP_TO_Z_UP = new Matrix4().makeRotationX(-Math.PI / 2).multiply(new Matrix4().makeScale(1, -1, 1));
|
|
2366
|
+
/**
|
|
2367
|
+
* A system that handles the rendering of external 3D model defs (e.g. GLB/glTF).
|
|
2368
|
+
* Loads models asynchronously, builds one {@link BatchedMesh} per (ModelDef, source
|
|
2369
|
+
* material) pair so each material clone can carry its def's opacity directly, and
|
|
2370
|
+
* routes shading through {@link MaterialSystem.patchMaterial} for dim + polygon offset.
|
|
2371
|
+
*/
|
|
2372
|
+
var ModelSystem = class extends RenderableSystem {
|
|
2373
|
+
loader = new GLTFLoader();
|
|
2374
|
+
cachePromise = /* @__PURE__ */ new Map();
|
|
2375
|
+
pendingUpdates = /* @__PURE__ */ new Map();
|
|
2376
|
+
layerAborted = /* @__PURE__ */ new WeakSet();
|
|
2377
|
+
isDisposed = false;
|
|
2378
|
+
/**
|
|
2379
|
+
* @param ctx {@link RendererContext}
|
|
2380
|
+
* @param materialSystem {@link MaterialSystem}
|
|
2381
|
+
*/
|
|
2382
|
+
constructor(ctx, materialSystem) {
|
|
2383
|
+
super("model", ctx, logger$11);
|
|
2384
|
+
this.materialSystem = materialSystem;
|
|
2385
|
+
const dracoLoader = new DRACOLoader();
|
|
2386
|
+
dracoLoader.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.5.7/");
|
|
2387
|
+
this.loader.setDRACOLoader(dracoLoader);
|
|
2388
|
+
this.loader.setMeshoptDecoder(MeshoptDecoder);
|
|
2389
|
+
}
|
|
2390
|
+
buildLayer(layer) {
|
|
2391
|
+
const group = new Group();
|
|
2392
|
+
if (layer.children.length === 0) return group;
|
|
2393
|
+
this.loadAndBuild(group, layer.children);
|
|
2394
|
+
return group;
|
|
2395
|
+
}
|
|
2396
|
+
updateDef(def, firstUpdate = false) {
|
|
2397
|
+
if (!this.getObjectInstanceByDef(def)) {
|
|
2398
|
+
this.pendingUpdates.set(def, def);
|
|
2399
|
+
return;
|
|
2400
|
+
}
|
|
2401
|
+
super.updateDef(def, firstUpdate);
|
|
2402
|
+
}
|
|
2403
|
+
disposeLayer(group) {
|
|
2404
|
+
this.layerAborted.add(group);
|
|
2405
|
+
super.disposeLayer(group);
|
|
2406
|
+
}
|
|
2407
|
+
dispose() {
|
|
2408
|
+
this.isDisposed = true;
|
|
2409
|
+
super.dispose();
|
|
2410
|
+
for (const cachePromise of this.cachePromise.values()) cachePromise.then((cache) => disposeGltfCache(cache)).catch(() => void 0);
|
|
2411
|
+
this.cachePromise.clear();
|
|
2412
|
+
this.pendingUpdates.clear();
|
|
2413
|
+
}
|
|
2414
|
+
disposeObject(object) {
|
|
2415
|
+
disposeObject(object, { textures: false });
|
|
2416
|
+
}
|
|
2417
|
+
updateDefImpl(def, mesh, instanceIds) {
|
|
2418
|
+
const extents = mesh.userData["modelExtents"];
|
|
2419
|
+
if (extents) {
|
|
2420
|
+
const matrix = composeInstanceMatrix(def, extents);
|
|
2421
|
+
for (const instanceId of instanceIds) mesh.setMatrixAt(instanceId, matrix);
|
|
2422
|
+
}
|
|
2423
|
+
const material = mesh.material;
|
|
2424
|
+
const opacity = def.opacity ?? 1;
|
|
2425
|
+
material.opacity = opacity;
|
|
2426
|
+
const srcTransparent = material.userData["srcTransparent"] === true;
|
|
2427
|
+
material.transparent = opacity < 1 || srcTransparent;
|
|
2428
|
+
material.depthWrite = !material.transparent;
|
|
2429
|
+
}
|
|
2430
|
+
async loadAndBuild(group, models) {
|
|
2431
|
+
let caches;
|
|
2432
|
+
try {
|
|
2433
|
+
caches = await Promise.all(models.map((def) => this.loadCached(def.url)));
|
|
2434
|
+
} catch (err) {
|
|
2435
|
+
logger$11.warn("Failed to load model batch %O", err);
|
|
2436
|
+
return;
|
|
2437
|
+
}
|
|
2438
|
+
if (this.isDisposed || this.layerAborted.has(group)) return;
|
|
2439
|
+
const batches = [];
|
|
2440
|
+
for (let i = 0; i < models.length; i++) {
|
|
2441
|
+
const cache = caches[i];
|
|
2442
|
+
if (!cache || cache.entries.length === 0) continue;
|
|
2443
|
+
batches.push(...this.buildBatchesForDef(models[i], cache));
|
|
2444
|
+
}
|
|
2445
|
+
if (this.isDisposed || this.layerAborted.has(group)) {
|
|
2446
|
+
for (const batch of batches) {
|
|
2447
|
+
this.disposeObject(batch);
|
|
2448
|
+
this.unregisterObject(batch);
|
|
2449
|
+
}
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
group.add(...batches);
|
|
2453
|
+
for (const batch of batches) batch.renderOrder = group.renderOrder;
|
|
2454
|
+
for (const [def, latest] of this.pendingUpdates) if (this.getObjectInstanceByDef(def)) {
|
|
2455
|
+
this.pendingUpdates.delete(def);
|
|
2456
|
+
this.updateDef(latest);
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
buildBatchesForDef(def, cache) {
|
|
2460
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
2461
|
+
for (const entry of cache.entries) {
|
|
2462
|
+
const key = `${entry.sourceMaterial.uuid}|${attributeSignature(entry.geometry)}`;
|
|
2463
|
+
let bucket = buckets.get(key);
|
|
2464
|
+
if (!bucket) {
|
|
2465
|
+
bucket = {
|
|
2466
|
+
sourceMaterial: entry.sourceMaterial,
|
|
2467
|
+
geometries: []
|
|
2468
|
+
};
|
|
2469
|
+
buckets.set(key, bucket);
|
|
2470
|
+
}
|
|
2471
|
+
bucket.geometries.push(entry.geometry);
|
|
2472
|
+
}
|
|
2473
|
+
const batches = [];
|
|
2474
|
+
for (const { sourceMaterial, geometries } of buckets.values()) {
|
|
2475
|
+
const material = this.createPatchedMaterial(sourceMaterial);
|
|
2476
|
+
let totalVertices = 0;
|
|
2477
|
+
let totalIndices = 0;
|
|
2478
|
+
for (const geometry of geometries) {
|
|
2479
|
+
totalVertices += geometry.attributes["position"].count;
|
|
2480
|
+
totalIndices += geometry.index?.count ?? 0;
|
|
2481
|
+
}
|
|
2482
|
+
const batch = new BatchedMesh$1(geometries.length, totalVertices, totalIndices, material);
|
|
2483
|
+
batch.userData["modelExtents"] = cache.extents;
|
|
2484
|
+
for (const geometry of geometries) {
|
|
2485
|
+
const geometryId = batch.addGeometry(geometry);
|
|
2486
|
+
const instanceId = batch.addInstance(geometryId);
|
|
2487
|
+
this.registerDefInstance(def, batch, instanceId);
|
|
2488
|
+
}
|
|
2489
|
+
batches.push(batch);
|
|
2490
|
+
}
|
|
2491
|
+
return batches;
|
|
2492
|
+
}
|
|
2493
|
+
createPatchedMaterial(source) {
|
|
2494
|
+
const physical = new MeshPhysicalMaterial();
|
|
2495
|
+
if (source instanceof MeshStandardMaterial || source instanceof MeshPhysicalMaterial) {
|
|
2496
|
+
physical.color.copy(source.color);
|
|
2497
|
+
physical.map = source.map;
|
|
2498
|
+
physical.normalMap = source.normalMap;
|
|
2499
|
+
physical.normalScale.copy(source.normalScale);
|
|
2500
|
+
physical.roughness = source.roughness;
|
|
2501
|
+
physical.roughnessMap = source.roughnessMap;
|
|
2502
|
+
physical.metalness = source.metalness;
|
|
2503
|
+
physical.metalnessMap = source.metalnessMap;
|
|
2504
|
+
physical.emissive.copy(source.emissive);
|
|
2505
|
+
physical.emissiveIntensity = source.emissiveIntensity;
|
|
2506
|
+
physical.emissiveMap = source.emissiveMap;
|
|
2507
|
+
physical.aoMap = source.aoMap;
|
|
2508
|
+
physical.aoMapIntensity = source.aoMapIntensity;
|
|
2509
|
+
physical.alphaMap = source.alphaMap;
|
|
2510
|
+
physical.alphaTest = source.alphaTest;
|
|
2511
|
+
}
|
|
2512
|
+
physical.side = source.side;
|
|
2513
|
+
physical.transparent = source.transparent;
|
|
2514
|
+
physical.opacity = source.opacity;
|
|
2515
|
+
physical.depthWrite = !source.transparent;
|
|
2516
|
+
physical.depthFunc = source.transparent ? LessDepth : LessEqualDepth;
|
|
2517
|
+
physical.userData["srcTransparent"] = source.transparent;
|
|
2518
|
+
this.materialSystem.patchMaterial(physical, { lightingEnable: true });
|
|
2519
|
+
return physical;
|
|
2520
|
+
}
|
|
2521
|
+
async loadCached(url) {
|
|
2522
|
+
let promise = this.cachePromise.get(url);
|
|
2523
|
+
if (!promise) {
|
|
2524
|
+
promise = this.parseGltf(url);
|
|
2525
|
+
this.cachePromise.set(url, promise);
|
|
2526
|
+
}
|
|
2527
|
+
try {
|
|
2528
|
+
return await promise;
|
|
2529
|
+
} catch (err) {
|
|
2530
|
+
logger$11.warn("Failed to load model %s: %O", url, err);
|
|
2531
|
+
this.cachePromise.delete(url);
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
async parseGltf(url) {
|
|
2536
|
+
const entries = collectGltfEntries(await this.loader.loadAsync(url));
|
|
2537
|
+
const extents = new Box3();
|
|
2538
|
+
for (const { geometry } of entries) {
|
|
2539
|
+
if (!geometry.boundingBox) geometry.computeBoundingBox();
|
|
2540
|
+
extents.union(geometry.boundingBox);
|
|
2541
|
+
}
|
|
2542
|
+
return {
|
|
2543
|
+
extents,
|
|
2544
|
+
entries
|
|
2545
|
+
};
|
|
2546
|
+
}
|
|
2547
|
+
};
|
|
2548
|
+
/**
|
|
2549
|
+
* Traverses a loaded GLTF scene and collects baked geometries paired with their
|
|
2550
|
+
* source materials. Geometries are cloned, then transformed by
|
|
2551
|
+
* {@link Y_UP_TO_Z_UP} times the node's world matrix so vertices land in engine
|
|
2552
|
+
* space relative to the GLB root.
|
|
2553
|
+
* @param gltf parsed GLTF
|
|
2554
|
+
* @returns array of `{geometry, sourceMaterial}` entries, one per Mesh node
|
|
2555
|
+
*/
|
|
2556
|
+
function collectGltfEntries(gltf) {
|
|
2557
|
+
const entries = [];
|
|
2558
|
+
gltf.scene.updateMatrixWorld(true);
|
|
2559
|
+
const tmp = new Matrix4();
|
|
2560
|
+
gltf.scene.traverse((node) => {
|
|
2561
|
+
if (!(node instanceof Mesh)) return;
|
|
2562
|
+
const mesh = node;
|
|
2563
|
+
if (!mesh.geometry || !mesh.material) return;
|
|
2564
|
+
const sourceMaterial = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;
|
|
2565
|
+
if (Array.isArray(mesh.material) && mesh.material.length > 1) logger$11.debug("Multi-material mesh on node %s — only the first material is used.", mesh.name);
|
|
2566
|
+
const baked = mesh.geometry.clone();
|
|
2567
|
+
tmp.multiplyMatrices(Y_UP_TO_Z_UP, mesh.matrixWorld);
|
|
2568
|
+
baked.applyMatrix4(tmp);
|
|
2569
|
+
baked.computeBoundingBox();
|
|
2570
|
+
baked.computeBoundingSphere();
|
|
2571
|
+
entries.push({
|
|
2572
|
+
geometry: baked,
|
|
2573
|
+
sourceMaterial
|
|
2574
|
+
});
|
|
2575
|
+
});
|
|
2576
|
+
return entries;
|
|
2577
|
+
}
|
|
2578
|
+
/**
|
|
2579
|
+
* Disposes a cached GLB's baked geometries and the unique textures referenced by its
|
|
2580
|
+
* source materials. Source textures are shared across batches, so this is the only
|
|
2581
|
+
* place they should be released.
|
|
2582
|
+
* @param cache cache entry to dispose
|
|
2583
|
+
*/
|
|
2584
|
+
function disposeGltfCache(cache) {
|
|
2585
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2586
|
+
const textureProps = [
|
|
2587
|
+
"map",
|
|
2588
|
+
"normalMap",
|
|
2589
|
+
"roughnessMap",
|
|
2590
|
+
"metalnessMap",
|
|
2591
|
+
"emissiveMap",
|
|
2592
|
+
"aoMap",
|
|
2593
|
+
"alphaMap"
|
|
2594
|
+
];
|
|
2595
|
+
for (const { sourceMaterial } of cache.entries) {
|
|
2596
|
+
const matRecord = sourceMaterial;
|
|
2597
|
+
for (const prop of textureProps) {
|
|
2598
|
+
const tex = matRecord[prop];
|
|
2599
|
+
if (tex && tex instanceof Texture && !seen.has(tex)) {
|
|
2600
|
+
seen.add(tex);
|
|
2601
|
+
tex.dispose();
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
for (const { geometry } of cache.entries) geometry.dispose();
|
|
2606
|
+
}
|
|
2607
|
+
/**
|
|
2608
|
+
* Stable identifier for a geometry's attribute set. Used to sub-bucket geometries that
|
|
2609
|
+
* share a source material but have different attribute names (e.g. with vs without uv),
|
|
2610
|
+
* since {@link BatchedMesh.addGeometry} requires homogeneous attributes per batch.
|
|
2611
|
+
* @param geometry geometry to summarize
|
|
2612
|
+
* @returns comma-separated sorted attribute names
|
|
2613
|
+
*/
|
|
2614
|
+
function attributeSignature(geometry) {
|
|
2615
|
+
return Object.keys(geometry.attributes).sort().join(",");
|
|
2616
|
+
}
|
|
2617
|
+
/**
|
|
2618
|
+
* Composes a per-instance world matrix from a ModelDef's bounds and the loaded GLB's
|
|
2619
|
+
* engine-space extents: scale uniformly to fit bounds.size in XY, rotate around Z by
|
|
2620
|
+
* bounds.rotation, and translate so the model sits at bounds.center with its base at
|
|
2621
|
+
* bounds.elevation.
|
|
2622
|
+
* @param def model definition
|
|
2623
|
+
* @param extents engine-space bounding box of the loaded GLB
|
|
2624
|
+
* @returns world matrix to set on a BatchedMesh instance
|
|
2625
|
+
*/
|
|
2626
|
+
function composeInstanceMatrix(def, extents) {
|
|
2627
|
+
const size = extents.getSize(tempSize);
|
|
2628
|
+
const center = extents.getCenter(tempCenter);
|
|
2629
|
+
const sx = size.x > 0 ? def.bounds.size.x / size.x : 1;
|
|
2630
|
+
const sy = size.y > 0 ? def.bounds.size.y / size.y : 1;
|
|
2631
|
+
const scale = Math.min(sx, sy);
|
|
2632
|
+
return new Matrix4().makeTranslation(-center.x, -center.y, -extents.min.z).premultiply(tempScale.makeScale(scale, scale, scale)).premultiply(tempRotation.makeRotationZ(def.bounds.rotation)).premultiply(tempTranslation.makeTranslation(def.bounds.center.x, def.bounds.center.y, def.bounds.elevation));
|
|
2633
|
+
}
|
|
2634
|
+
var tempSize = new Vector3();
|
|
2635
|
+
var tempCenter = new Vector3();
|
|
2636
|
+
var tempScale = new Matrix4();
|
|
2637
|
+
var tempRotation = new Matrix4();
|
|
2638
|
+
var tempTranslation = new Matrix4();
|
|
2266
2639
|
//#endregion
|
|
2267
2640
|
//#region src/geometry/text.ts
|
|
2268
|
-
var logger$
|
|
2641
|
+
var logger$10 = createLogger("text");
|
|
2269
2642
|
/**
|
|
2270
2643
|
* A system that handles the rendering of text defs.
|
|
2271
2644
|
*/
|
|
@@ -2286,7 +2659,7 @@ var TextSystem = class extends RenderableSystem {
|
|
|
2286
2659
|
* @param materialSystem {@link MaterialSystem}
|
|
2287
2660
|
*/
|
|
2288
2661
|
constructor(ctx, materialSystem) {
|
|
2289
|
-
super("text", ctx, logger$
|
|
2662
|
+
super("text", ctx, logger$10);
|
|
2290
2663
|
this.materialSystem = materialSystem;
|
|
2291
2664
|
}
|
|
2292
2665
|
dispose() {
|
|
@@ -2364,6 +2737,9 @@ var TextSystem = class extends RenderableSystem {
|
|
|
2364
2737
|
const textDefs = layer.children;
|
|
2365
2738
|
const batchedText = new BatchedText$1();
|
|
2366
2739
|
batchedText.material = this.materialSystem.createColorMaterial({ depthEnable: is3D });
|
|
2740
|
+
batchedText.material.transparent = false;
|
|
2741
|
+
batchedText.material.blending = CustomBlending;
|
|
2742
|
+
batchedText.material.blendSrcAlpha = OneFactor;
|
|
2367
2743
|
const mappingData = [];
|
|
2368
2744
|
let instanceId = 0;
|
|
2369
2745
|
for (const textDef of textDefs) {
|
|
@@ -2458,7 +2834,7 @@ function setAnchorsAndAlignment(text, alignment) {
|
|
|
2458
2834
|
}
|
|
2459
2835
|
//#endregion
|
|
2460
2836
|
//#region src/geometry/layer.ts
|
|
2461
|
-
var logger$
|
|
2837
|
+
var logger$9 = createLogger("layer");
|
|
2462
2838
|
/**
|
|
2463
2839
|
* A system that handles the rendering of layer defs and scene graph building.
|
|
2464
2840
|
*/
|
|
@@ -2469,6 +2845,7 @@ var LayerSystem = class {
|
|
|
2469
2845
|
imageSystem;
|
|
2470
2846
|
textSystem;
|
|
2471
2847
|
lineSystem;
|
|
2848
|
+
modelSystem;
|
|
2472
2849
|
systems;
|
|
2473
2850
|
mapLayerDefsToObjects = /* @__PURE__ */ new Map();
|
|
2474
2851
|
mapLayerDefToParent = /* @__PURE__ */ new Map();
|
|
@@ -2483,11 +2860,13 @@ var LayerSystem = class {
|
|
|
2483
2860
|
this.imageSystem = new ImageSystem(this.ctx, this.materialSystem);
|
|
2484
2861
|
this.textSystem = new TextSystem(this.ctx, this.materialSystem);
|
|
2485
2862
|
this.lineSystem = new LineSystem(this.ctx, this.materialSystem);
|
|
2863
|
+
this.modelSystem = new ModelSystem(this.ctx, this.materialSystem);
|
|
2486
2864
|
this.systems = [
|
|
2487
2865
|
this.meshSystem,
|
|
2488
2866
|
this.imageSystem,
|
|
2489
2867
|
this.textSystem,
|
|
2490
|
-
this.lineSystem
|
|
2868
|
+
this.lineSystem,
|
|
2869
|
+
this.modelSystem
|
|
2491
2870
|
];
|
|
2492
2871
|
}
|
|
2493
2872
|
/**
|
|
@@ -2513,14 +2892,14 @@ var LayerSystem = class {
|
|
|
2513
2892
|
*/
|
|
2514
2893
|
buildScene(sceneDef) {
|
|
2515
2894
|
const renderOrder = this.initRenderOrder(sceneDef.rootLayer).map((layer) => this.getFullLayerName(layer));
|
|
2516
|
-
logger$
|
|
2895
|
+
logger$9.debug("Render order %O", renderOrder);
|
|
2517
2896
|
const rootGroup = new Group();
|
|
2518
2897
|
rootGroup.name = sceneDef.rootLayer.name;
|
|
2519
2898
|
this.mapLayerDefsToObjects.set(sceneDef.rootLayer, rootGroup);
|
|
2520
2899
|
if (sceneDef.background) rootGroup.add(this.createBackgroundLayer(sceneDef.background));
|
|
2521
2900
|
for (const child of sceneDef.rootLayer.children) rootGroup.add(this.buildLayer(child));
|
|
2522
2901
|
this.updateLayer(sceneDef.rootLayer, false);
|
|
2523
|
-
printTree(rootGroup, logger$
|
|
2902
|
+
printTree(rootGroup, logger$9.debug);
|
|
2524
2903
|
if (sceneDef.memoryLimit) this.imageSystem.memoryLimitMb = sceneDef.memoryLimit;
|
|
2525
2904
|
this.imageSystem.renderAtlases();
|
|
2526
2905
|
return rootGroup;
|
|
@@ -2553,6 +2932,7 @@ var LayerSystem = class {
|
|
|
2553
2932
|
else if (isImageDef(def)) this.imageSystem.updateDef(def);
|
|
2554
2933
|
else if (isTextDef(def)) this.textSystem.updateDef(def);
|
|
2555
2934
|
else if (isLineDef(def)) this.lineSystem.updateDef(def);
|
|
2935
|
+
else if (isModelDef(def)) this.modelSystem.updateDef(def);
|
|
2556
2936
|
else this.updateLayer(def, true);
|
|
2557
2937
|
}
|
|
2558
2938
|
disposeLayerTree(layerDef) {
|
|
@@ -2563,6 +2943,7 @@ var LayerSystem = class {
|
|
|
2563
2943
|
else if (isImageLayer(layerDef)) this.imageSystem.disposeLayer(layerObject);
|
|
2564
2944
|
else if (isTextLayer(layerDef)) this.textSystem.disposeLayer(layerObject);
|
|
2565
2945
|
else if (isLineLayer(layerDef)) this.lineSystem.disposeLayer(layerObject);
|
|
2946
|
+
else if (isModelLayer(layerDef)) this.modelSystem.disposeLayer(layerObject);
|
|
2566
2947
|
this.mapLayerDefsToObjects.delete(layerDef);
|
|
2567
2948
|
this.mapLayerDefToParent.delete(layerDef);
|
|
2568
2949
|
this.renderOrderMap.delete(layerDef);
|
|
@@ -2576,6 +2957,7 @@ var LayerSystem = class {
|
|
|
2576
2957
|
else if (isLineLayer(layerDef)) this.lineSystem.updateLayer(group, layerDef);
|
|
2577
2958
|
else if (isTextLayer(layerDef)) this.textSystem.updateLayer(group, layerDef);
|
|
2578
2959
|
else if (isShapeLayer(layerDef)) this.meshSystem.updateLayer(group, layerDef);
|
|
2960
|
+
else if (isModelLayer(layerDef)) this.modelSystem.updateLayer(group, layerDef);
|
|
2579
2961
|
this.setLayerName(group, group.name);
|
|
2580
2962
|
}
|
|
2581
2963
|
layerObject.visible = !layerDef.hidden && layerDef.children.length > 0;
|
|
@@ -2585,12 +2967,13 @@ var LayerSystem = class {
|
|
|
2585
2967
|
}
|
|
2586
2968
|
buildLayer(layerDef, parentPrefix = "") {
|
|
2587
2969
|
const layerFullName = parentPrefix + layerDef.name;
|
|
2588
|
-
logger$
|
|
2970
|
+
logger$9.debug(`Building layer ${layerFullName}...`);
|
|
2589
2971
|
let layerObject;
|
|
2590
2972
|
if (isShapeLayer(layerDef)) layerObject = this.meshSystem.buildLayer(layerDef);
|
|
2591
2973
|
else if (isImageLayer(layerDef)) layerObject = this.imageSystem.buildLayer(layerDef);
|
|
2592
2974
|
else if (isTextLayer(layerDef)) layerObject = this.textSystem.buildLayer(layerDef);
|
|
2593
2975
|
else if (isLineLayer(layerDef)) layerObject = this.lineSystem.buildLayer(layerDef);
|
|
2976
|
+
else if (isModelLayer(layerDef)) layerObject = this.modelSystem.buildLayer(layerDef);
|
|
2594
2977
|
else {
|
|
2595
2978
|
layerObject = new Group();
|
|
2596
2979
|
layerDef.children.map((layer) => this.buildLayer(layer, parentPrefix + `${layerDef.name}:`)).forEach((g) => layerObject.add(g));
|
|
@@ -2610,11 +2993,8 @@ var LayerSystem = class {
|
|
|
2610
2993
|
setRenderOrder(object, layer) {
|
|
2611
2994
|
const renderOrder = this.renderOrderMap.get(layer);
|
|
2612
2995
|
if (renderOrder === void 0) return;
|
|
2613
|
-
if (object.isGroup) {
|
|
2614
|
-
object.children.forEach((child) => this.setRenderOrder(child, layer));
|
|
2615
|
-
return;
|
|
2616
|
-
}
|
|
2617
2996
|
object.renderOrder = renderOrder;
|
|
2997
|
+
if (object.isGroup) object.children.forEach((child) => this.setRenderOrder(child, layer));
|
|
2618
2998
|
}
|
|
2619
2999
|
createBackgroundLayer(color) {
|
|
2620
3000
|
const backgroundMesh = new Mesh(new PlaneGeometry(2, 2), this.materialSystem.createBackgroundMaterial(color));
|
|
@@ -2670,219 +3050,154 @@ var LayerSystem = class {
|
|
|
2670
3050
|
//#region src/geometry/lights.ts
|
|
2671
3051
|
/** System for managing directional lights in 3D scenes. */
|
|
2672
3052
|
var LightsSystem = class {
|
|
2673
|
-
|
|
2674
|
-
|
|
3053
|
+
skyColor = 16777215;
|
|
3054
|
+
groundColor = 0;
|
|
3055
|
+
intensity = Math.PI;
|
|
2675
3056
|
mapSceneToLight = /* @__PURE__ */ new Map();
|
|
3057
|
+
pmremGenerator;
|
|
3058
|
+
/**
|
|
3059
|
+
* @param ctx {@link RendererContext} instance
|
|
3060
|
+
*/
|
|
3061
|
+
constructor(ctx) {
|
|
3062
|
+
this.ctx = ctx;
|
|
3063
|
+
this.pmremGenerator = new PMREMGenerator(this.ctx.three);
|
|
3064
|
+
}
|
|
2676
3065
|
/**
|
|
2677
3066
|
* Initializes a directional light for the given scene.
|
|
2678
3067
|
* @param scene {@link Scene} to add the light to
|
|
2679
3068
|
*/
|
|
2680
3069
|
initLights(scene) {
|
|
2681
|
-
const
|
|
2682
|
-
|
|
2683
|
-
const
|
|
3070
|
+
const hemisphereLightIntensity = this.intensity * .25;
|
|
3071
|
+
const hemisphereLight = new HemisphereLight(this.skyColor, this.groundColor, hemisphereLightIntensity);
|
|
3072
|
+
const matrixWorldInverse = new Matrix4().copy(scene.matrixWorld).invert();
|
|
3073
|
+
const position = new Vector3(0, 0, -1).applyMatrix4(matrixWorldInverse);
|
|
3074
|
+
hemisphereLight.position.copy(position);
|
|
3075
|
+
scene.add(hemisphereLight);
|
|
3076
|
+
const directionalLightIntensity = this.intensity * .5;
|
|
3077
|
+
const directionalLight = new DirectionalLight(this.skyColor, directionalLightIntensity);
|
|
2684
3078
|
scene.add(directionalLight);
|
|
2685
3079
|
scene.add(directionalLight.target);
|
|
2686
3080
|
this.mapSceneToLight.set(scene, directionalLight);
|
|
3081
|
+
scene.environment = this.pmremGenerator.fromScene(new ColorEnvironment(), .04).texture;
|
|
3082
|
+
scene.environmentIntensity = .25;
|
|
2687
3083
|
}
|
|
2688
3084
|
/**
|
|
2689
3085
|
* Updates the light direction to match the camera's viewing direction.
|
|
2690
|
-
* @param
|
|
3086
|
+
* @param sceneState {@link SceneState} containing the light
|
|
2691
3087
|
* @param camera {@link Camera} to derive the light direction from
|
|
2692
3088
|
*/
|
|
2693
|
-
updateLights(
|
|
2694
|
-
const light = this.mapSceneToLight.get(scene);
|
|
3089
|
+
updateLights(sceneState, camera) {
|
|
3090
|
+
const light = this.mapSceneToLight.get(sceneState.scene);
|
|
2695
3091
|
if (!light) return;
|
|
2696
|
-
|
|
2697
|
-
camera.getWorldDirection(light.position).multiply({
|
|
2698
|
-
x: Math.sign(e[0]),
|
|
2699
|
-
y: Math.sign(e[5]),
|
|
2700
|
-
z: Math.sign(e[10])
|
|
2701
|
-
}).normalize().negate();
|
|
3092
|
+
camera.getWorldDirection(light.position).transformDirection(sceneState.worldMatrixInverse).negate();
|
|
2702
3093
|
}
|
|
2703
3094
|
};
|
|
2704
3095
|
//#endregion
|
|
2705
|
-
//#region src/
|
|
2706
|
-
|
|
2707
|
-
var logger$7 = createLogger("");
|
|
3096
|
+
//#region src/loaders/glb.ts
|
|
3097
|
+
var logger$8 = createLogger("glb");
|
|
2708
3098
|
/**
|
|
2709
|
-
*
|
|
2710
|
-
* @param
|
|
2711
|
-
* @
|
|
2712
|
-
* @returns false if disposed, true otherwise
|
|
3099
|
+
* Loads a GLB file and returns a Three.js model.
|
|
3100
|
+
* @param models - The models to load.
|
|
3101
|
+
* @returns An object containing parsed Three.js objects.
|
|
2713
3102
|
*/
|
|
2714
|
-
function
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
}
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
3103
|
+
async function loadGLB(models) {
|
|
3104
|
+
const loader = new GLTFLoader();
|
|
3105
|
+
const dracoLoader = new DRACOLoader();
|
|
3106
|
+
dracoLoader.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.5.7/");
|
|
3107
|
+
const root = new Group();
|
|
3108
|
+
for (const model of models) {
|
|
3109
|
+
if (model.isMeshopt) loader.setMeshoptDecoder(MeshoptDecoder);
|
|
3110
|
+
if (model.isDraco) loader.setDRACOLoader(dracoLoader);
|
|
3111
|
+
const loadedModel = await loader.loadAsync(model.url);
|
|
3112
|
+
logger$8.info("model loaded", loadedModel);
|
|
3113
|
+
root.add(...loadedModel.scene.children);
|
|
3114
|
+
loader.setMeshoptDecoder(null);
|
|
3115
|
+
loader.setDRACOLoader(null);
|
|
3116
|
+
}
|
|
3117
|
+
const bounds = new Box3();
|
|
3118
|
+
root.traverse((node) => {
|
|
3119
|
+
if (node.userData && Object.keys(node.userData).filter((key) => key !== "name").length > 0) logger$8.info(`node ${node.name} userData:`, node.userData);
|
|
3120
|
+
if (node instanceof Mesh && node.geometry) {
|
|
3121
|
+
const materialJSON = node.material.toJSON();
|
|
3122
|
+
logger$8.info(`node ${node.name} material %O:`, materialJSON);
|
|
3123
|
+
const geometry = node.geometry;
|
|
3124
|
+
if (!geometry.boundingBox) geometry.computeBoundingBox();
|
|
3125
|
+
const box = geometry.boundingBox.clone();
|
|
3126
|
+
box.applyMatrix4(node.matrixWorld);
|
|
3127
|
+
bounds.union(box);
|
|
3128
|
+
}
|
|
3129
|
+
});
|
|
3130
|
+
logger$8.info("model bounds", bounds);
|
|
3131
|
+
root.rotation.x = -Math.PI / 2;
|
|
3132
|
+
root.scale.set(1, -1, 1);
|
|
3133
|
+
return {
|
|
3134
|
+
sceneDef: {
|
|
3135
|
+
rootLayer: {
|
|
3136
|
+
children: [],
|
|
3137
|
+
name: "root"
|
|
3138
|
+
},
|
|
3139
|
+
viewbox: new Rect([bounds.min.x, bounds.min.z], [bounds.max.x, bounds.max.z])
|
|
3140
|
+
},
|
|
3141
|
+
root
|
|
3142
|
+
};
|
|
2746
3143
|
}
|
|
2747
3144
|
/**
|
|
2748
|
-
*
|
|
2749
|
-
*
|
|
2750
|
-
* @param
|
|
2751
|
-
* @
|
|
3145
|
+
* Loads a GLB file, converts it to a Three.js {@link Scene}, exports the scene as a GLTF
|
|
3146
|
+
* (JSON) file, and triggers a browser download of the result.
|
|
3147
|
+
* @param url - The URL of the GLB file.
|
|
3148
|
+
* @param isMeshopt - Whether the model file uses meshopt compression.
|
|
2752
3149
|
*/
|
|
2753
|
-
function
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
3150
|
+
async function glb2gltf(url, isMeshopt) {
|
|
3151
|
+
const loader = new GLTFLoader();
|
|
3152
|
+
if (isMeshopt) loader.setMeshoptDecoder(MeshoptDecoder);
|
|
3153
|
+
const model = await loader.loadAsync(url);
|
|
3154
|
+
logger$8.info("glb loaded for export", model);
|
|
3155
|
+
const scene = new Scene();
|
|
3156
|
+
for (const child of model.scene.children) scene.add(child.clone());
|
|
3157
|
+
const gltf = await new GLTFExporter().parseAsync(scene);
|
|
3158
|
+
logger$8.info("scene exported as gltf");
|
|
3159
|
+
const filename = `${deriveBasename(url)}.gltf`;
|
|
3160
|
+
saveBlob(new Blob([JSON.stringify(gltf, null, 2)], { type: "application/json" }), filename);
|
|
2759
3161
|
}
|
|
2760
3162
|
/**
|
|
2761
|
-
*
|
|
2762
|
-
*
|
|
2763
|
-
*
|
|
2764
|
-
*
|
|
2765
|
-
*
|
|
3163
|
+
* Loads a GLTF/GLB file and splits its top-level children into two GLB files based on
|
|
3164
|
+
* whether each child's name starts with `BasisTransform`. The matching children are saved
|
|
3165
|
+
* with a `-decor` suffix; the rest are saved with a `-base` suffix. Both files are
|
|
3166
|
+
* triggered as browser downloads. The output is intentionally uncompressed — run a
|
|
3167
|
+
* meshopt/quantize pass offline (e.g. via `gltf-transform`) afterwards.
|
|
3168
|
+
* @param url - The URL of the GLTF/GLB file.
|
|
2766
3169
|
*/
|
|
2767
|
-
function
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
3170
|
+
async function splitGltf(url) {
|
|
3171
|
+
const model = await new GLTFLoader().loadAsync(url);
|
|
3172
|
+
logger$8.info("gltf loaded for split", model);
|
|
3173
|
+
const baseScene = new Scene();
|
|
3174
|
+
const decorScene = new Scene();
|
|
3175
|
+
for (const child of model.scene.children) (child.name.startsWith("BasisTransform") ? decorScene : baseScene).add(child.clone());
|
|
3176
|
+
logger$8.info("split: base=%d decor=%d", baseScene.children.length, decorScene.children.length);
|
|
3177
|
+
const exporter = new GLTFExporter();
|
|
3178
|
+
const baseGlb = await exporter.parseAsync(baseScene, { binary: true });
|
|
3179
|
+
const decorGlb = await exporter.parseAsync(decorScene, { binary: true });
|
|
3180
|
+
const stem = deriveBasename(url);
|
|
3181
|
+
saveBlob(new Blob([baseGlb], { type: "model/gltf-binary" }), `${stem}-base.glb`);
|
|
3182
|
+
saveBlob(new Blob([decorGlb], { type: "model/gltf-binary" }), `${stem}-decor.glb`);
|
|
2773
3183
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
* When the guard (or a per-method override) fails, the corresponding fallback
|
|
2777
|
-
* method is called instead of the real implementation.
|
|
2778
|
-
* Non-function properties are passed through from `impl` (if present) or `fallback`.
|
|
2779
|
-
* @param impl - Real implementation, or null when the backing system doesn't exist
|
|
2780
|
-
* @param fallback - Noop / default implementation used when guard fails or impl is null
|
|
2781
|
-
* @param guard - Default guard applied to every method
|
|
2782
|
-
* @param guards - Optional per-method guard overrides (takes precedence over `guard`)
|
|
2783
|
-
* @returns A new object conforming to T with every method wrapped
|
|
2784
|
-
*/
|
|
2785
|
-
function guardAPI(impl, fallback, guard, guards) {
|
|
2786
|
-
const guarded = {};
|
|
2787
|
-
for (const [key, fallbackVal] of Object.entries(fallback)) if (typeof fallbackVal === "function") {
|
|
2788
|
-
const implFn = impl?.[key];
|
|
2789
|
-
const methodGuard = guards?.[key] ?? guard;
|
|
2790
|
-
const fallbackFn = fallbackVal;
|
|
2791
|
-
guarded[key] = (...args) => {
|
|
2792
|
-
if (methodGuard(key) && implFn) return implFn(...args);
|
|
2793
|
-
return fallbackFn(...args);
|
|
2794
|
-
};
|
|
2795
|
-
} else guarded[key] = impl?.[key] ?? fallbackVal;
|
|
2796
|
-
return guarded;
|
|
3184
|
+
function deriveBasename(url) {
|
|
3185
|
+
return (url.split("?")[0].split("#")[0].split("/").pop() ?? "model").replace(/\.(glb|gltf)$/i, "");
|
|
2797
3186
|
}
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
ndcLeft = new Vector2(-1, 0);
|
|
2809
|
-
ndcRight = new Vector2(1, 0);
|
|
2810
|
-
intersectionPointLeft = new Vector3();
|
|
2811
|
-
intersectionPointRight = new Vector3();
|
|
2812
|
-
prevZoomFactor;
|
|
2813
|
-
/**
|
|
2814
|
-
* @param ctx {@link RendererContext} instance
|
|
2815
|
-
* @param pickingSystem {@link PickingSystem} instance
|
|
2816
|
-
*/
|
|
2817
|
-
constructor(ctx, pickingSystem) {
|
|
2818
|
-
this.ctx = ctx;
|
|
2819
|
-
this.pickingSystem = pickingSystem;
|
|
2820
|
-
}
|
|
2821
|
-
/** Current zoom factor estimation */
|
|
2822
|
-
get zoomFactor() {
|
|
2823
|
-
const estimatedZoomFactor = this.estimateZoomFactor();
|
|
2824
|
-
const newZoomFactor = estimatedZoomFactor ?? this.prevZoomFactor ?? 1;
|
|
2825
|
-
if (estimatedZoomFactor) this.prevZoomFactor = estimatedZoomFactor;
|
|
2826
|
-
return newZoomFactor;
|
|
2827
|
-
}
|
|
2828
|
-
/**
|
|
2829
|
-
* Set the camera projection matrix.
|
|
2830
|
-
* @param matrix Projection matrix
|
|
2831
|
-
*/
|
|
2832
|
-
setCameraProjection(matrix) {
|
|
2833
|
-
if (!assertValidMatrix(matrix, "setCameraProjection", externalLogger)) return;
|
|
2834
|
-
this.camera.projectionMatrix.fromArray(matrix);
|
|
2835
|
-
this.camera.projectionMatrixInverse.copy(this.camera.projectionMatrix).invert();
|
|
2836
|
-
}
|
|
2837
|
-
estimateZoomFactor() {
|
|
2838
|
-
const [bufferW, bufferH] = this.ctx.getDrawingBufferSizePx();
|
|
2839
|
-
if (bufferW <= 0 || bufferH <= 0) return;
|
|
2840
|
-
const worldPoint1 = this.pickingSystem.intersectPlane(this.ndcLeft, this.camera, this.intersectionPointLeft);
|
|
2841
|
-
const worldPoint2 = this.pickingSystem.intersectPlane(this.ndcRight, this.camera, this.intersectionPointRight);
|
|
2842
|
-
if (!worldPoint1 || !worldPoint2) return;
|
|
2843
|
-
const worldLength = worldPoint2.sub(worldPoint1).length();
|
|
2844
|
-
if (worldLength === 0) return;
|
|
2845
|
-
return bufferW / worldLength;
|
|
2846
|
-
}
|
|
2847
|
-
};
|
|
2848
|
-
var originalProjectionMatrix = new Matrix4();
|
|
2849
|
-
var patchedMeshes = /* @__PURE__ */ new WeakSet();
|
|
2850
|
-
/**
|
|
2851
|
-
* Three.js passes two matrices to the vertex shader as uniforms: projectionMatrix and modelViewMatrix.
|
|
2852
|
-
* In external mode, the scale of matrix values might be (in mapbox case it certainly is) too big for float32 precision,
|
|
2853
|
-
* thus introducing visual artifacts after multiplying them. This function introduces a workaround to premultiply
|
|
2854
|
-
* matrices on the CPU side and pass them as a single uniform. In order to not break the shader, it passes an identity
|
|
2855
|
-
* matrix as a second uniform. It uses material.onBeforeRender instead of object.onBeforeRender because it's called
|
|
2856
|
-
* after three.js own matrix calculations (https://github.com/mrdoob/three.js/blob/33b6ce05a72be0b49cc84cdbb7b5cc972036eebc/src/renderers/WebGLRenderer.js#L2097).
|
|
2857
|
-
* Since there is no material.onAfterRender, we use object.onAfterRender to restore camera projection matrix instead.
|
|
2858
|
-
* Idempotent — already-patched meshes are skipped via a WeakSet, so this can safely be called every frame.
|
|
2859
|
-
* @param scene {@link Scene} instance
|
|
2860
|
-
*/
|
|
2861
|
-
function patchMatricesInExternalMode(scene) {
|
|
2862
|
-
scene.traverse((child) => {
|
|
2863
|
-
const mesh = child;
|
|
2864
|
-
if (!mesh.isMesh || patchedMeshes.has(mesh)) return;
|
|
2865
|
-
patchedMeshes.add(mesh);
|
|
2866
|
-
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
|
|
2867
|
-
for (const material of materials) {
|
|
2868
|
-
const onBeforeRender = material.onBeforeRender.bind(material);
|
|
2869
|
-
material.onBeforeRender = (renderer, scene, camera, geometry, object, group) => {
|
|
2870
|
-
onBeforeRender(renderer, scene, camera, geometry, object, group);
|
|
2871
|
-
originalProjectionMatrix.copy(camera.projectionMatrix);
|
|
2872
|
-
camera.projectionMatrix.multiply(object.matrixWorld);
|
|
2873
|
-
object.modelViewMatrix.identity();
|
|
2874
|
-
};
|
|
2875
|
-
}
|
|
2876
|
-
const onAfterRender = mesh.onAfterRender.bind(mesh);
|
|
2877
|
-
mesh.onAfterRender = (renderer, scene, camera, geometry, object, group) => {
|
|
2878
|
-
camera.projectionMatrix.copy(originalProjectionMatrix);
|
|
2879
|
-
onAfterRender(renderer, scene, camera, geometry, object, group);
|
|
2880
|
-
};
|
|
2881
|
-
});
|
|
3187
|
+
function saveBlob(blob, filename) {
|
|
3188
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
3189
|
+
const link = document.createElement("a");
|
|
3190
|
+
link.href = objectUrl;
|
|
3191
|
+
link.download = filename;
|
|
3192
|
+
link.style.display = "none";
|
|
3193
|
+
document.body.appendChild(link);
|
|
3194
|
+
link.click();
|
|
3195
|
+
document.body.removeChild(link);
|
|
3196
|
+
URL.revokeObjectURL(objectUrl);
|
|
2882
3197
|
}
|
|
2883
3198
|
//#endregion
|
|
2884
3199
|
//#region src/ui/controls.ts
|
|
2885
|
-
var logger$
|
|
3200
|
+
var logger$7 = createLogger("controls");
|
|
2886
3201
|
/** Navigation system. Manages camera controls and zooming. */
|
|
2887
3202
|
var ControlsSystem = class {
|
|
2888
3203
|
controller;
|
|
@@ -2926,7 +3241,7 @@ var ControlsSystem = class {
|
|
|
2926
3241
|
const worldRect = new Rect(this.coordinatesSystem.modelToWorld(rect.min), this.coordinatesSystem.modelToWorld(rect.max));
|
|
2927
3242
|
const sourceRect = Polygon.fromRect(worldRect).rotate(bearingAngle).bounds;
|
|
2928
3243
|
if (sourceRect.size.x <= 0 || sourceRect.size.y <= 0) {
|
|
2929
|
-
logger$
|
|
3244
|
+
logger$7.warn("zoomTo: sourceRect size is 0");
|
|
2930
3245
|
return;
|
|
2931
3246
|
}
|
|
2932
3247
|
if (paddingPercent) targetRect.expand({
|
|
@@ -3225,7 +3540,7 @@ function asEventAPI(system) {
|
|
|
3225
3540
|
}
|
|
3226
3541
|
//#endregion
|
|
3227
3542
|
//#region src/space/coordinates.ts
|
|
3228
|
-
var logger$
|
|
3543
|
+
var logger$6 = createLogger("coordinates");
|
|
3229
3544
|
/** System responsible for converting coordinates between different spaces. */
|
|
3230
3545
|
var CoordinatesSystem = class {
|
|
3231
3546
|
/** Used as a scratch vector for coordinate space conversions */
|
|
@@ -3298,7 +3613,7 @@ var CoordinatesSystem = class {
|
|
|
3298
3613
|
canvasToNDC(point, out = new Vector2()) {
|
|
3299
3614
|
const [w, h] = this.ctx.getDrawingBufferSizePx();
|
|
3300
3615
|
if (w <= 0 || h <= 0) {
|
|
3301
|
-
logger$
|
|
3616
|
+
logger$6.warn("canvasToNDC: renderer size is 0");
|
|
3302
3617
|
return out.set(0, 0);
|
|
3303
3618
|
}
|
|
3304
3619
|
const dpr = this.ctx.three.getPixelRatio();
|
|
@@ -3466,7 +3781,7 @@ function createNoopHandler() {
|
|
|
3466
3781
|
};
|
|
3467
3782
|
}
|
|
3468
3783
|
//#endregion
|
|
3469
|
-
//#region ../../node_modules/.pnpm/camera-controls@3.1.1_patch_hash=1d30d1431514ed87b48b2ec3defd4fe64eca4cb9b29373199021281dbc5d7782_three@0.
|
|
3784
|
+
//#region ../../node_modules/.pnpm/camera-controls@3.1.1_patch_hash=1d30d1431514ed87b48b2ec3defd4fe64eca4cb9b29373199021281dbc5d7782_three@0.184.0/node_modules/camera-controls/dist/camera-controls.module.js
|
|
3470
3785
|
/*!
|
|
3471
3786
|
* camera-controls
|
|
3472
3787
|
* https://github.com/yomotsu/camera-controls
|
|
@@ -5838,7 +6153,7 @@ var subsetOfTHREE = {
|
|
|
5838
6153
|
Plane
|
|
5839
6154
|
};
|
|
5840
6155
|
CameraControls.install({ THREE: subsetOfTHREE });
|
|
5841
|
-
var logger$
|
|
6156
|
+
var logger$5 = createLogger("cameraController");
|
|
5842
6157
|
/**
|
|
5843
6158
|
* Minimum z-axis separation between overlapping surfaces, in SVG/model units.
|
|
5844
6159
|
*
|
|
@@ -5912,7 +6227,7 @@ var CameraController = class CameraController extends CameraControls {
|
|
|
5912
6227
|
radius
|
|
5913
6228
|
].map((value) => +value.toFixed(2));
|
|
5914
6229
|
const clippingPlanes = [this.camera.near, this.camera.far];
|
|
5915
|
-
logger$
|
|
6230
|
+
logger$5.debug("camera update %O", {
|
|
5916
6231
|
position,
|
|
5917
6232
|
target,
|
|
5918
6233
|
spherical,
|
|
@@ -6567,6 +6882,100 @@ var noopHandlers = {
|
|
|
6567
6882
|
pitch: createNoopHandler()
|
|
6568
6883
|
};
|
|
6569
6884
|
//#endregion
|
|
6885
|
+
//#region src/util/asserts.ts
|
|
6886
|
+
/** Logger instance for assert warnings */
|
|
6887
|
+
var logger$4 = createLogger("");
|
|
6888
|
+
/**
|
|
6889
|
+
* Asserts the renderer has not been disposed.
|
|
6890
|
+
* @param renderer - Renderer instance to check
|
|
6891
|
+
* @param funcName - Name of the calling function for error messages
|
|
6892
|
+
* @returns false if disposed, true otherwise
|
|
6893
|
+
*/
|
|
6894
|
+
function assertNotDisposed(renderer, funcName) {
|
|
6895
|
+
if (renderer.isDisposed) {
|
|
6896
|
+
logger$4.warn(`[${funcName}]: Renderer is used after being disposed. Please create a new instance.`);
|
|
6897
|
+
return false;
|
|
6898
|
+
}
|
|
6899
|
+
return true;
|
|
6900
|
+
}
|
|
6901
|
+
/**
|
|
6902
|
+
* Asserts the renderer has been initialized.
|
|
6903
|
+
* @param renderer - Renderer instance to check
|
|
6904
|
+
* @param funcName - Name of the calling function for error messages
|
|
6905
|
+
* @returns false if not initialized, true otherwise
|
|
6906
|
+
*/
|
|
6907
|
+
function assertInitialized(renderer, funcName) {
|
|
6908
|
+
if (!renderer.isInitialized) {
|
|
6909
|
+
logger$4.warn(`${funcName}: Renderer is not initialized. Please call init() before using it.`);
|
|
6910
|
+
return false;
|
|
6911
|
+
}
|
|
6912
|
+
return true;
|
|
6913
|
+
}
|
|
6914
|
+
/**
|
|
6915
|
+
* Asserts the renderer has not been initialized yet.
|
|
6916
|
+
* @param renderer - Renderer instance to check
|
|
6917
|
+
* @param funcName - Name of the calling function for error messages
|
|
6918
|
+
* @returns false if already initialized, true otherwise
|
|
6919
|
+
*/
|
|
6920
|
+
function assertNotInitialized(renderer, funcName) {
|
|
6921
|
+
if (renderer.isInitialized) {
|
|
6922
|
+
logger$4.warn(`${funcName}: Renderer is already initialized. Please call init() only once.`);
|
|
6923
|
+
return false;
|
|
6924
|
+
}
|
|
6925
|
+
return true;
|
|
6926
|
+
}
|
|
6927
|
+
/**
|
|
6928
|
+
* Asserts the renderer is not in external mode.
|
|
6929
|
+
* @param renderer - Renderer instance to check
|
|
6930
|
+
* @param funcName - Name of the calling function for error messages
|
|
6931
|
+
* @returns false if in external mode, true otherwise
|
|
6932
|
+
*/
|
|
6933
|
+
function assertNotExternalMode(renderer, funcName) {
|
|
6934
|
+
if (renderer.isExternalMode) {
|
|
6935
|
+
logger$4.warn(`${funcName}: This operation is not supported in external mode.`);
|
|
6936
|
+
return false;
|
|
6937
|
+
}
|
|
6938
|
+
return true;
|
|
6939
|
+
}
|
|
6940
|
+
/**
|
|
6941
|
+
* Asserts that number array is a valid 4x4 matrix.
|
|
6942
|
+
* @param matrix number array to check
|
|
6943
|
+
* @param name name of the matrix for error messages
|
|
6944
|
+
* @param log optional logger instance to use for warning messages (defaults to root logger)
|
|
6945
|
+
* @returns true if matrix is valid, false otherwise
|
|
6946
|
+
*/
|
|
6947
|
+
function assertValidMatrix(matrix, name, log = logger$4) {
|
|
6948
|
+
if (matrix.length !== 16) {
|
|
6949
|
+
log.warn(`${name}: Matrix must be 16 elements long`);
|
|
6950
|
+
return false;
|
|
6951
|
+
}
|
|
6952
|
+
return true;
|
|
6953
|
+
}
|
|
6954
|
+
/**
|
|
6955
|
+
* Wraps an API object so that every method is guarded by a precondition check.
|
|
6956
|
+
* When the guard (or a per-method override) fails, the corresponding fallback
|
|
6957
|
+
* method is called instead of the real implementation.
|
|
6958
|
+
* Non-function properties are passed through from `impl` (if present) or `fallback`.
|
|
6959
|
+
* @param impl - Real implementation, or null when the backing system doesn't exist
|
|
6960
|
+
* @param fallback - Noop / default implementation used when guard fails or impl is null
|
|
6961
|
+
* @param guard - Default guard applied to every method
|
|
6962
|
+
* @param guards - Optional per-method guard overrides (takes precedence over `guard`)
|
|
6963
|
+
* @returns A new object conforming to T with every method wrapped
|
|
6964
|
+
*/
|
|
6965
|
+
function guardAPI(impl, fallback, guard, guards) {
|
|
6966
|
+
const guarded = {};
|
|
6967
|
+
for (const [key, fallbackVal] of Object.entries(fallback)) if (typeof fallbackVal === "function") {
|
|
6968
|
+
const implFn = impl?.[key];
|
|
6969
|
+
const methodGuard = guards?.[key] ?? guard;
|
|
6970
|
+
const fallbackFn = fallbackVal;
|
|
6971
|
+
guarded[key] = (...args) => {
|
|
6972
|
+
if (methodGuard(key) && implFn) return implFn(...args);
|
|
6973
|
+
return fallbackFn(...args);
|
|
6974
|
+
};
|
|
6975
|
+
} else guarded[key] = impl?.[key] ?? fallbackVal;
|
|
6976
|
+
return guarded;
|
|
6977
|
+
}
|
|
6978
|
+
//#endregion
|
|
6570
6979
|
//#region src/core/updates.ts
|
|
6571
6980
|
var logger$3 = createLogger("updates");
|
|
6572
6981
|
/**
|
|
@@ -6752,6 +7161,85 @@ var InternalCameraSystem = class {
|
|
|
6752
7161
|
}
|
|
6753
7162
|
};
|
|
6754
7163
|
//#endregion
|
|
7164
|
+
//#region src/space/external.ts
|
|
7165
|
+
var externalLogger = createLogger("external");
|
|
7166
|
+
/**
|
|
7167
|
+
* Camera system in external mode. Exposes dummy camera instance as a wrapper for external camera transforms,
|
|
7168
|
+
* and estimates zoom factor ndc -> world conversion.
|
|
7169
|
+
*/
|
|
7170
|
+
var ExternalCameraSystem = class {
|
|
7171
|
+
/** External camera instance */
|
|
7172
|
+
camera = new Camera();
|
|
7173
|
+
ndcLeft = new Vector2(-1, 0);
|
|
7174
|
+
ndcRight = new Vector2(1, 0);
|
|
7175
|
+
intersectionPointLeft = new Vector3();
|
|
7176
|
+
intersectionPointRight = new Vector3();
|
|
7177
|
+
prevZoomFactor;
|
|
7178
|
+
mainMatrix4 = new Matrix4();
|
|
7179
|
+
mainMatrixInverse = new Matrix4();
|
|
7180
|
+
nearPoint = new Vector3();
|
|
7181
|
+
farPoint = new Vector3();
|
|
7182
|
+
viewDir = new Vector3();
|
|
7183
|
+
target = new Vector3();
|
|
7184
|
+
up = new Vector3();
|
|
7185
|
+
cameraPos = new Vector3();
|
|
7186
|
+
linearSystem = new Matrix3();
|
|
7187
|
+
rhs = new Vector3();
|
|
7188
|
+
/**
|
|
7189
|
+
* @param ctx {@link RendererContext} instance
|
|
7190
|
+
* @param pickingSystem {@link PickingSystem} instance
|
|
7191
|
+
*/
|
|
7192
|
+
constructor(ctx, pickingSystem) {
|
|
7193
|
+
this.ctx = ctx;
|
|
7194
|
+
this.pickingSystem = pickingSystem;
|
|
7195
|
+
this.camera.matrixAutoUpdate = false;
|
|
7196
|
+
this.camera.matrixWorldAutoUpdate = false;
|
|
7197
|
+
}
|
|
7198
|
+
/** Current zoom factor estimation */
|
|
7199
|
+
get zoomFactor() {
|
|
7200
|
+
const estimatedZoomFactor = this.estimateZoomFactor();
|
|
7201
|
+
const newZoomFactor = estimatedZoomFactor ?? this.prevZoomFactor ?? 1;
|
|
7202
|
+
if (estimatedZoomFactor) this.prevZoomFactor = estimatedZoomFactor;
|
|
7203
|
+
return newZoomFactor;
|
|
7204
|
+
}
|
|
7205
|
+
/**
|
|
7206
|
+
* Decompose the combined world-to-clip-space matrix into a rigid view matrix and a projection
|
|
7207
|
+
* matrix, then publish all four matrices (and their inverses) on the underlying camera.
|
|
7208
|
+
* @param mainMatrix Combined matrix mapping world space directly to clip space.
|
|
7209
|
+
*/
|
|
7210
|
+
setCameraProjection(mainMatrix) {
|
|
7211
|
+
if (!assertValidMatrix(mainMatrix, "setCameraProjection", externalLogger)) return;
|
|
7212
|
+
const M = this.mainMatrix4.fromArray(mainMatrix);
|
|
7213
|
+
const e = M.elements;
|
|
7214
|
+
this.mainMatrixInverse.copy(M).invert();
|
|
7215
|
+
this.linearSystem.set(e[0], e[4], e[8], e[1], e[5], e[9], e[3], e[7], e[11]);
|
|
7216
|
+
this.rhs.set(-e[12], -e[13], -e[15]);
|
|
7217
|
+
this.cameraPos.copy(this.rhs).applyMatrix3(this.linearSystem.invert());
|
|
7218
|
+
this.nearPoint.set(0, 0, 1).applyMatrix4(this.mainMatrixInverse);
|
|
7219
|
+
this.farPoint.set(0, 0, -1).applyMatrix4(this.mainMatrixInverse);
|
|
7220
|
+
this.viewDir.copy(this.farPoint).sub(this.nearPoint).normalize();
|
|
7221
|
+
this.up.set(0, 0, 1);
|
|
7222
|
+
if (Math.abs(this.up.dot(this.viewDir)) > .999) this.up.set(0, 1, 0);
|
|
7223
|
+
this.target.copy(this.cameraPos).add(this.viewDir);
|
|
7224
|
+
const camera = this.camera;
|
|
7225
|
+
camera.matrixWorld.lookAt(this.cameraPos, this.target, this.up);
|
|
7226
|
+
camera.matrixWorld.setPosition(this.cameraPos);
|
|
7227
|
+
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
|
|
7228
|
+
camera.projectionMatrix.multiplyMatrices(M, camera.matrixWorld);
|
|
7229
|
+
camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
|
|
7230
|
+
}
|
|
7231
|
+
estimateZoomFactor() {
|
|
7232
|
+
const [bufferW, bufferH] = this.ctx.getDrawingBufferSizePx();
|
|
7233
|
+
if (bufferW <= 0 || bufferH <= 0) return;
|
|
7234
|
+
const worldPoint1 = this.pickingSystem.intersectPlane(this.ndcLeft, this.camera, this.intersectionPointLeft);
|
|
7235
|
+
const worldPoint2 = this.pickingSystem.intersectPlane(this.ndcRight, this.camera, this.intersectionPointRight);
|
|
7236
|
+
if (!worldPoint1 || !worldPoint2) return;
|
|
7237
|
+
const worldLength = worldPoint2.sub(worldPoint1).length();
|
|
7238
|
+
if (worldLength === 0) return;
|
|
7239
|
+
return bufferW / worldLength;
|
|
7240
|
+
}
|
|
7241
|
+
};
|
|
7242
|
+
//#endregion
|
|
6755
7243
|
//#region src/space/picking.ts
|
|
6756
7244
|
/**
|
|
6757
7245
|
* Picking subsystem.
|
|
@@ -6826,7 +7314,8 @@ var SceneSystem = class {
|
|
|
6826
7314
|
* @returns Scale factor (model space to world space)
|
|
6827
7315
|
*/
|
|
6828
7316
|
scaleFactor(sceneId) {
|
|
6829
|
-
|
|
7317
|
+
const sceneState = this.getSceneStateById(sceneId);
|
|
7318
|
+
return Math.abs(sceneState.worldMatrix.elements[0]);
|
|
6830
7319
|
}
|
|
6831
7320
|
/**
|
|
6832
7321
|
* Register a new scene from it's definition
|
|
@@ -7194,7 +7683,7 @@ var Renderer = class {
|
|
|
7194
7683
|
this.renderer.autoClear = !this.isExternalMode;
|
|
7195
7684
|
this.ctx = this.createRendererContext();
|
|
7196
7685
|
this.eventSystem = new EventSystem();
|
|
7197
|
-
this.lightsSystem = new LightsSystem();
|
|
7686
|
+
this.lightsSystem = new LightsSystem(this.ctx);
|
|
7198
7687
|
this.layerSystem = new LayerSystem(this.ctx);
|
|
7199
7688
|
this.viewportSystem = new ViewportSystem(this.ctx, this.eventSystem);
|
|
7200
7689
|
this.updatesSystem = new UpdatesSystem(this.ctx, this.layerSystem);
|
|
@@ -7293,6 +7782,18 @@ var Renderer = class {
|
|
|
7293
7782
|
return this.disposed;
|
|
7294
7783
|
}
|
|
7295
7784
|
/**
|
|
7785
|
+
* Loads a GLB file and returns a Three.js model.
|
|
7786
|
+
* @param models - The models to load.
|
|
7787
|
+
*/
|
|
7788
|
+
async loadModel(models) {
|
|
7789
|
+
this.init();
|
|
7790
|
+
const { root, sceneDef } = await loadGLB(models);
|
|
7791
|
+
const scene = this.viewportSystem.initScene(sceneDef);
|
|
7792
|
+
scene.background = new Color(15461355);
|
|
7793
|
+
scene.add(root);
|
|
7794
|
+
this.lightsSystem.initLights(scene);
|
|
7795
|
+
}
|
|
7796
|
+
/**
|
|
7296
7797
|
* Initializes viewport and scene with the given scene definition.
|
|
7297
7798
|
* Should be called once on startup. Repeated calls will produce console warnings.
|
|
7298
7799
|
* @param sceneDef {@link SceneDef} to render
|
|
@@ -7361,6 +7862,7 @@ var Renderer = class {
|
|
|
7361
7862
|
} else if (sceneState.scene.children.length === 0) {
|
|
7362
7863
|
const root = this.layerSystem.buildScene(sceneState.sceneDef);
|
|
7363
7864
|
scene.add(root);
|
|
7865
|
+
this.lightsSystem.initLights(scene);
|
|
7364
7866
|
}
|
|
7365
7867
|
}
|
|
7366
7868
|
const justLoaded = loadedChanged && sceneState.loaded;
|
|
@@ -7368,8 +7870,7 @@ var Renderer = class {
|
|
|
7368
7870
|
this.viewportSystem.updatePtScale(id);
|
|
7369
7871
|
const hasDefsUpdated = this.updatesSystem.processPendingUpdates(frustum, 3);
|
|
7370
7872
|
const forceRedraw = hasControlsUpdated || hasDefsUpdated || justLoaded || this.isExternalMode || this.ui;
|
|
7371
|
-
|
|
7372
|
-
this.lightsSystem.updateLights(scene, camera);
|
|
7873
|
+
this.lightsSystem.updateLights(sceneState, camera);
|
|
7373
7874
|
if (this.needsRedraw || forceRedraw) this.renderer.render(scene, camera);
|
|
7374
7875
|
if (hasDefsUpdated || this.needsRedraw) this.viewportSystem.invalidateSceneBounds(sceneState);
|
|
7375
7876
|
}
|
|
@@ -7475,4 +7976,4 @@ var Renderer = class {
|
|
|
7475
7976
|
}
|
|
7476
7977
|
};
|
|
7477
7978
|
//#endregion
|
|
7478
|
-
export { Polygon, Rect, Renderer, isImageDef, isImageLayer, isLayerDef, isLayerLayer, isLineDef, isLineLayer, isShapeDef, isShapeLayer, isTextDef, isTextLayer };
|
|
7979
|
+
export { Polygon, Rect, Renderer, glb2gltf, isImageDef, isImageLayer, isLayerDef, isLayerLayer, isLineDef, isLineLayer, isModelDef, isModelLayer, isShapeDef, isShapeLayer, isTextDef, isTextLayer, splitGltf };
|