@expofp/renderer 3.1.5 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +48 -1
- package/dist/index.js +771 -281
- 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);
|
|
@@ -1067,7 +1070,6 @@ var dimColorFrag = `
|
|
|
1067
1070
|
var POLYGON_OFFSET_MULTIPLIER = 12;
|
|
1068
1071
|
var sharedParameters = {
|
|
1069
1072
|
side: DoubleSide,
|
|
1070
|
-
transparent: true,
|
|
1071
1073
|
forceSinglePass: true,
|
|
1072
1074
|
depthFunc: AlwaysDepth
|
|
1073
1075
|
};
|
|
@@ -1075,6 +1077,7 @@ var sharedParameters = {
|
|
|
1075
1077
|
var MaterialSystem = class {
|
|
1076
1078
|
backgroundMaterial;
|
|
1077
1079
|
viewport = new Vector4();
|
|
1080
|
+
lightingMaterials = [];
|
|
1078
1081
|
/**
|
|
1079
1082
|
* Creates a line material.
|
|
1080
1083
|
* @param params {@link LineMaterialParameters}
|
|
@@ -1103,8 +1106,7 @@ var MaterialSystem = class {
|
|
|
1103
1106
|
uniforms["resolution"].value.set(this.viewport.z, this.viewport.w);
|
|
1104
1107
|
}
|
|
1105
1108
|
};
|
|
1106
|
-
|
|
1107
|
-
this.addPolygonOffset(material);
|
|
1109
|
+
this.patchMaterial(material);
|
|
1108
1110
|
return material;
|
|
1109
1111
|
}
|
|
1110
1112
|
/**
|
|
@@ -1113,14 +1115,21 @@ var MaterialSystem = class {
|
|
|
1113
1115
|
* @returns MeshBasicMaterial instance
|
|
1114
1116
|
*/
|
|
1115
1117
|
createColorMaterial(params = {}) {
|
|
1116
|
-
const
|
|
1118
|
+
const materialConstructor = params.lightingEnable ? MeshPhysicalMaterial : MeshBasicMaterial;
|
|
1119
|
+
const color = params.color ?? 16777215;
|
|
1120
|
+
const opacity = params.opacity ?? 1;
|
|
1121
|
+
const transparent = opacity < 1;
|
|
1122
|
+
const material = new materialConstructor({
|
|
1117
1123
|
...sharedParameters,
|
|
1118
|
-
color
|
|
1119
|
-
opacity
|
|
1124
|
+
color,
|
|
1125
|
+
opacity,
|
|
1126
|
+
transparent
|
|
1120
1127
|
});
|
|
1121
|
-
if (
|
|
1122
|
-
|
|
1123
|
-
|
|
1128
|
+
if (transparent) {
|
|
1129
|
+
material.depthWrite = false;
|
|
1130
|
+
material.depthFunc = LessDepth;
|
|
1131
|
+
} else if (params.depthEnable) material.depthFunc = LessEqualDepth;
|
|
1132
|
+
this.patchMaterial(material, { lightingEnable: params.lightingEnable });
|
|
1124
1133
|
return material;
|
|
1125
1134
|
}
|
|
1126
1135
|
/**
|
|
@@ -1131,18 +1140,37 @@ var MaterialSystem = class {
|
|
|
1131
1140
|
createTextureMaterial(params) {
|
|
1132
1141
|
const material = new MeshBasicMaterial({
|
|
1133
1142
|
...sharedParameters,
|
|
1134
|
-
map: params.map
|
|
1143
|
+
map: params.map,
|
|
1144
|
+
transparent: true,
|
|
1145
|
+
depthFunc: LessDepth
|
|
1135
1146
|
});
|
|
1136
|
-
if (params.depthEnable) material.depthFunc = LessEqualDepth;
|
|
1137
1147
|
if (params.uvOffset) material.onBeforeCompile = (shader) => {
|
|
1138
1148
|
shader.vertexShader = shader.vertexShader.replace("#include <uv_vertex>", `
|
|
1139
1149
|
#include <uv_vertex>
|
|
1140
1150
|
vMapUv = uv * uvOffset.zw + uvOffset.xy;
|
|
1141
1151
|
`);
|
|
1142
1152
|
};
|
|
1153
|
+
this.patchMaterial(material);
|
|
1154
|
+
return material;
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* Apply the engine's cross-cutting material patches: dim shader hooks, polygon offset,
|
|
1158
|
+
* and (optional) registration with the lighting system. Used internally by every
|
|
1159
|
+
* factory in this class and exposed publicly so externally-built materials (e.g. PBR
|
|
1160
|
+
* materials cloned from GLTF source) can opt in to the same behavior.
|
|
1161
|
+
* @param material material to patch
|
|
1162
|
+
* @param opts patch options
|
|
1163
|
+
* @param opts.lightingEnable when true and the material is a MeshPhysicalMaterial,
|
|
1164
|
+
* configures it for the project's lighting pipeline and registers it
|
|
1165
|
+
*/
|
|
1166
|
+
patchMaterial(material, opts = {}) {
|
|
1143
1167
|
addDimToMaterial(material);
|
|
1144
1168
|
this.addPolygonOffset(material);
|
|
1145
|
-
|
|
1169
|
+
if (opts.lightingEnable && material instanceof MeshPhysicalMaterial) {
|
|
1170
|
+
material.ior = 1;
|
|
1171
|
+
material.specularIntensity = 0;
|
|
1172
|
+
this.lightingMaterials.push(material);
|
|
1173
|
+
}
|
|
1146
1174
|
}
|
|
1147
1175
|
/**
|
|
1148
1176
|
* Creates a background material. Used for the background layer to support dimming the background.
|
|
@@ -1213,6 +1241,9 @@ function isTextDef(def) {
|
|
|
1213
1241
|
function isLineDef(def) {
|
|
1214
1242
|
return def.points !== void 0;
|
|
1215
1243
|
}
|
|
1244
|
+
function isModelDef(def) {
|
|
1245
|
+
return def.url !== void 0;
|
|
1246
|
+
}
|
|
1216
1247
|
function isLayerDef(def) {
|
|
1217
1248
|
return def.children !== void 0;
|
|
1218
1249
|
}
|
|
@@ -1228,6 +1259,9 @@ function isTextLayer(layer) {
|
|
|
1228
1259
|
function isLineLayer(layer) {
|
|
1229
1260
|
return layer.children[0] && isLineDef(layer.children[0]);
|
|
1230
1261
|
}
|
|
1262
|
+
function isModelLayer(layer) {
|
|
1263
|
+
return layer.children[0] && isModelDef(layer.children[0]);
|
|
1264
|
+
}
|
|
1231
1265
|
function isLayerLayer(layer) {
|
|
1232
1266
|
return layer.children[0] && isLayerDef(layer.children[0]);
|
|
1233
1267
|
}
|
|
@@ -1341,13 +1375,14 @@ var RenderableSystem = class {
|
|
|
1341
1375
|
* @param firstUpdate whether this is the first update for this def. Set to true when the def is first added to the scene.
|
|
1342
1376
|
*/
|
|
1343
1377
|
updateDef(def, firstUpdate = false) {
|
|
1344
|
-
const
|
|
1345
|
-
if (!
|
|
1346
|
-
const { object: mesh, instanceIds }
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1378
|
+
const mappings = this.getObjectInstanceByDef(def);
|
|
1379
|
+
if (!mappings) return;
|
|
1380
|
+
for (const { object: mesh, instanceIds } of mappings) {
|
|
1381
|
+
for (const instanceId of instanceIds) mesh.setVisibleAt(instanceId, !def.hidden);
|
|
1382
|
+
if (def.hidden && !firstUpdate) continue;
|
|
1383
|
+
for (const instanceId of instanceIds) toggleInstanceDim(mesh, instanceId, def.dim);
|
|
1384
|
+
this.updateDefImpl(def, mesh, instanceIds, firstUpdate);
|
|
1385
|
+
}
|
|
1351
1386
|
}
|
|
1352
1387
|
/**
|
|
1353
1388
|
* Update an existing collection with a new layer definition.
|
|
@@ -1418,7 +1453,8 @@ var RenderableSystem = class {
|
|
|
1418
1453
|
this.logger.debug(`Tried to register def with empty instanceIds %O`, def);
|
|
1419
1454
|
return;
|
|
1420
1455
|
}
|
|
1421
|
-
this.mapDefToObject.set(def,
|
|
1456
|
+
if (!this.mapDefToObject.has(def)) this.mapDefToObject.set(def, []);
|
|
1457
|
+
this.mapDefToObject.get(def).push({
|
|
1422
1458
|
object,
|
|
1423
1459
|
instanceIds: ids
|
|
1424
1460
|
});
|
|
@@ -1427,29 +1463,57 @@ var RenderableSystem = class {
|
|
|
1427
1463
|
this.updateDef(def, true);
|
|
1428
1464
|
}
|
|
1429
1465
|
/**
|
|
1430
|
-
*
|
|
1466
|
+
* Register a single instance mapping between a def and an object/instance, appending
|
|
1467
|
+
* one entry per call. Use when a single def maps to multiple instances across one or
|
|
1468
|
+
* more containers (e.g. a model spread across several BatchedMesh batches). Pushes
|
|
1469
|
+
* once per instance into the object→defs map so `getDefsByObject(obj)[batchId]` stays
|
|
1470
|
+
* aligned with sequential `BatchedMesh.addInstance` ids.
|
|
1471
|
+
* @param def def to register
|
|
1472
|
+
* @param object container the def's instance lives in
|
|
1473
|
+
* @param instanceId single instance id in the container
|
|
1474
|
+
*/
|
|
1475
|
+
registerDefInstance(def, object, instanceId) {
|
|
1476
|
+
if (!this.mapDefToObject.has(def)) this.mapDefToObject.set(def, []);
|
|
1477
|
+
this.mapDefToObject.get(def).push({
|
|
1478
|
+
object,
|
|
1479
|
+
instanceIds: [instanceId]
|
|
1480
|
+
});
|
|
1481
|
+
if (!this.mapObjectToDefs.has(object)) this.mapObjectToDefs.set(object, []);
|
|
1482
|
+
this.mapObjectToDefs.get(object).push(def);
|
|
1483
|
+
this.updateDef(def, true);
|
|
1484
|
+
}
|
|
1485
|
+
/**
|
|
1486
|
+
* Unregister a def and its associated object mappings.
|
|
1431
1487
|
* @param def def to unregister
|
|
1432
1488
|
*/
|
|
1433
1489
|
unregisterDef(def) {
|
|
1434
|
-
const
|
|
1435
|
-
if (
|
|
1436
|
-
const { object }
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1490
|
+
const mappings = this.mapDefToObject.get(def);
|
|
1491
|
+
if (mappings) {
|
|
1492
|
+
for (const { object } of mappings) {
|
|
1493
|
+
const defs = this.mapObjectToDefs.get(object);
|
|
1494
|
+
if (defs) {
|
|
1495
|
+
this.mapObjectToDefs.set(object, defs.filter((d) => d !== def));
|
|
1496
|
+
if (this.mapObjectToDefs.get(object).length === 0) this.mapObjectToDefs.delete(object);
|
|
1497
|
+
}
|
|
1441
1498
|
}
|
|
1442
1499
|
this.mapDefToObject.delete(def);
|
|
1443
1500
|
}
|
|
1444
1501
|
}
|
|
1445
1502
|
/**
|
|
1446
|
-
* Unregister all defs associated with an object.
|
|
1503
|
+
* Unregister all defs associated with an object. Only removes the entries that point
|
|
1504
|
+
* at this object — defs with mappings to other containers keep those entries intact.
|
|
1447
1505
|
* @param object object to unregister
|
|
1448
1506
|
*/
|
|
1449
1507
|
unregisterObject(object) {
|
|
1450
1508
|
const defs = this.mapObjectToDefs.get(object);
|
|
1451
1509
|
if (defs) {
|
|
1452
|
-
for (const def of defs)
|
|
1510
|
+
for (const def of defs) {
|
|
1511
|
+
const mappings = this.mapDefToObject.get(def);
|
|
1512
|
+
if (!mappings) continue;
|
|
1513
|
+
const remaining = mappings.filter((m) => m.object !== object);
|
|
1514
|
+
if (remaining.length === 0) this.mapDefToObject.delete(def);
|
|
1515
|
+
else this.mapDefToObject.set(def, remaining);
|
|
1516
|
+
}
|
|
1453
1517
|
this.mapObjectToDefs.delete(object);
|
|
1454
1518
|
}
|
|
1455
1519
|
}
|
|
@@ -1461,17 +1525,18 @@ var RenderableSystem = class {
|
|
|
1461
1525
|
this.mapObjectToDefs.clear();
|
|
1462
1526
|
}
|
|
1463
1527
|
/**
|
|
1464
|
-
* Lookup object/instance by def.
|
|
1528
|
+
* Lookup object/instance mappings by def. Returns an array because a single def can
|
|
1529
|
+
* map to multiple containers (e.g. a model spread across batches keyed by material).
|
|
1465
1530
|
* @param def def to lookup
|
|
1466
|
-
* @returns
|
|
1531
|
+
* @returns array of object/instance-id mappings, or undefined if none
|
|
1467
1532
|
*/
|
|
1468
1533
|
getObjectInstanceByDef(def) {
|
|
1469
|
-
const
|
|
1470
|
-
if (!
|
|
1534
|
+
const mappings = this.mapDefToObject.get(def);
|
|
1535
|
+
if (!mappings || mappings.length === 0) {
|
|
1471
1536
|
this.logger.debug(`No object mapping found for def %O`, def);
|
|
1472
1537
|
return;
|
|
1473
1538
|
}
|
|
1474
|
-
return
|
|
1539
|
+
return mappings;
|
|
1475
1540
|
}
|
|
1476
1541
|
/**
|
|
1477
1542
|
* Lookup defs by object.
|
|
@@ -1491,7 +1556,7 @@ var RenderableSystem = class {
|
|
|
1491
1556
|
};
|
|
1492
1557
|
//#endregion
|
|
1493
1558
|
//#region src/geometry/image.ts
|
|
1494
|
-
var logger$
|
|
1559
|
+
var logger$14 = createLogger("image");
|
|
1495
1560
|
/**
|
|
1496
1561
|
* A system that handles the rendering of image defs.
|
|
1497
1562
|
*/
|
|
@@ -1509,10 +1574,10 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1509
1574
|
* @param materialSystem {@link MaterialSystem}
|
|
1510
1575
|
*/
|
|
1511
1576
|
constructor(ctx, materialSystem) {
|
|
1512
|
-
super("image", ctx, logger$
|
|
1577
|
+
super("image", ctx, logger$14);
|
|
1513
1578
|
this.materialSystem = materialSystem;
|
|
1514
1579
|
const atlasTextureSize = ctx.three.capabilities.maxTextureSize;
|
|
1515
|
-
logger$
|
|
1580
|
+
logger$14.debug(`Max texture size: ${atlasTextureSize}`);
|
|
1516
1581
|
this.packer = new MaxRectsPacker(atlasTextureSize, atlasTextureSize, 1, { pot: false });
|
|
1517
1582
|
}
|
|
1518
1583
|
dispose() {
|
|
@@ -1583,7 +1648,7 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1583
1648
|
oldTexture?.dispose();
|
|
1584
1649
|
mesh.visible = true;
|
|
1585
1650
|
data.appliedScale = targetScale;
|
|
1586
|
-
logger$
|
|
1651
|
+
logger$14.debug(`Rendered atlas for ${mesh.name || mesh.parent?.name} at scale ${targetScale.toFixed(3)}`);
|
|
1587
1652
|
}
|
|
1588
1653
|
}
|
|
1589
1654
|
disposeObject(object) {
|
|
@@ -1603,7 +1668,7 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1603
1668
|
}
|
|
1604
1669
|
computeResizeFactor() {
|
|
1605
1670
|
if (!this.memoryLimitMb) {
|
|
1606
|
-
logger$
|
|
1671
|
+
logger$14.debug("Memory limit is not set, atlases will not be scaled");
|
|
1607
1672
|
return 1;
|
|
1608
1673
|
}
|
|
1609
1674
|
let totalResizable = 0;
|
|
@@ -1614,17 +1679,17 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1614
1679
|
else totalNonResizable += bytes;
|
|
1615
1680
|
}
|
|
1616
1681
|
if (totalResizable === 0) {
|
|
1617
|
-
logger$
|
|
1682
|
+
logger$14.debug("No resizable atlases, atlases will not be scaled");
|
|
1618
1683
|
return 1;
|
|
1619
1684
|
}
|
|
1620
1685
|
const budget = this.memoryLimitMb * 1024 * 1024 - totalNonResizable;
|
|
1621
1686
|
if (budget <= 0) {
|
|
1622
|
-
logger$
|
|
1687
|
+
logger$14.debug("Memory limit is too low, unable to resize textures.");
|
|
1623
1688
|
return 1;
|
|
1624
1689
|
}
|
|
1625
1690
|
const factor = Math.sqrt(budget / totalResizable);
|
|
1626
1691
|
if (factor >= 1) return 1;
|
|
1627
|
-
logger$
|
|
1692
|
+
logger$14.debug(`Resize factor: ${factor.toFixed(3)} (budget ${budget} bytes, resizable ${totalResizable} bytes)`);
|
|
1628
1693
|
return factor;
|
|
1629
1694
|
}
|
|
1630
1695
|
packImages(images) {
|
|
@@ -1646,8 +1711,8 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1646
1711
|
const boundsHeight = image.bounds.size.y;
|
|
1647
1712
|
const ratio = sourceArea / (boundsWidth * boundsHeight);
|
|
1648
1713
|
if (ratio > 1e3) {
|
|
1649
|
-
logger$
|
|
1650
|
-
logger$
|
|
1714
|
+
logger$14.debug(`Image bounds: ${boundsWidth}x${boundsHeight}`, `Image source: ${sourceWidth}x${sourceHeight}`);
|
|
1715
|
+
logger$14.warn(`Image bounds area is ${ratio.toFixed(2)} times smaller than the image.`);
|
|
1651
1716
|
}
|
|
1652
1717
|
const rect = new Rectangle(image.source.width, image.source.height);
|
|
1653
1718
|
rect.data = [imageWithIndex];
|
|
@@ -1656,7 +1721,7 @@ var ImageSystem = class extends RenderableSystem {
|
|
|
1656
1721
|
} else existingRect.data.push(imageWithIndex);
|
|
1657
1722
|
}
|
|
1658
1723
|
this.packer.addArray(rectangles);
|
|
1659
|
-
this.packer.bins.forEach((bin) => logger$
|
|
1724
|
+
this.packer.bins.forEach((bin) => logger$14.debug(`Bin: ${bin.width}x${bin.height}, ${bin.rects.length} rectangles`));
|
|
1660
1725
|
return this.packer.bins;
|
|
1661
1726
|
}
|
|
1662
1727
|
};
|
|
@@ -1668,7 +1733,7 @@ function createAtlas(bin, scale) {
|
|
|
1668
1733
|
const ctx = canvas.getContext("2d");
|
|
1669
1734
|
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
1735
|
const t1 = performance.now();
|
|
1671
|
-
logger$
|
|
1736
|
+
logger$14.debug(`Create atlas (${canvas.width}x${canvas.height}) took ${(t1 - t0).toFixed(2)} milliseconds.`);
|
|
1672
1737
|
return createTexture(canvas);
|
|
1673
1738
|
}
|
|
1674
1739
|
function createPlaceholderTexture() {
|
|
@@ -1687,7 +1752,7 @@ function getAtlasSizeBytes(width, height) {
|
|
|
1687
1752
|
}
|
|
1688
1753
|
//#endregion
|
|
1689
1754
|
//#region src/geometry/line.ts
|
|
1690
|
-
var logger$
|
|
1755
|
+
var logger$13 = createLogger("line");
|
|
1691
1756
|
/**
|
|
1692
1757
|
* A system that handles the rendering of line defs.
|
|
1693
1758
|
*/
|
|
@@ -1698,7 +1763,7 @@ var LineSystem = class extends RenderableSystem {
|
|
|
1698
1763
|
* @param materialSystem {@link MaterialSystem}
|
|
1699
1764
|
*/
|
|
1700
1765
|
constructor(ctx, materialSystem) {
|
|
1701
|
-
super("line", ctx, logger$
|
|
1766
|
+
super("line", ctx, logger$13);
|
|
1702
1767
|
this.materialSystem = materialSystem;
|
|
1703
1768
|
}
|
|
1704
1769
|
buildLayer(layer) {
|
|
@@ -2106,7 +2171,7 @@ function computeBoundingSphere(bounds, origin, out = new Sphere()) {
|
|
|
2106
2171
|
}
|
|
2107
2172
|
//#endregion
|
|
2108
2173
|
//#region src/geometry/mesh.ts
|
|
2109
|
-
var logger$
|
|
2174
|
+
var logger$12 = createLogger("mesh");
|
|
2110
2175
|
extend([namesPlugin]);
|
|
2111
2176
|
/**
|
|
2112
2177
|
* A system that handles the rendering of shape defs.
|
|
@@ -2125,7 +2190,7 @@ var MeshSystem = class extends RenderableSystem {
|
|
|
2125
2190
|
* @param materialSystem {@link MaterialSystem}
|
|
2126
2191
|
*/
|
|
2127
2192
|
constructor(ctx, materialSystem) {
|
|
2128
|
-
super("mesh", ctx, logger$
|
|
2193
|
+
super("mesh", ctx, logger$12);
|
|
2129
2194
|
this.materialSystem = materialSystem;
|
|
2130
2195
|
}
|
|
2131
2196
|
buildLayer(layer) {
|
|
@@ -2164,24 +2229,25 @@ var MeshSystem = class extends RenderableSystem {
|
|
|
2164
2229
|
const isPolygon = shape instanceof Polygon;
|
|
2165
2230
|
const isRect = shape instanceof Rect;
|
|
2166
2231
|
if (expectedShapeType === "polygon" && !isPolygon || expectedShapeType === "rect" && !isRect) {
|
|
2167
|
-
logger$
|
|
2232
|
+
logger$12.warn("Shape type changing not supported %O", shapeDef);
|
|
2168
2233
|
return;
|
|
2169
2234
|
}
|
|
2170
2235
|
if (isPolygon) {
|
|
2171
2236
|
const geometryRange = mesh.getGeometryRangeAt(geometryId);
|
|
2172
|
-
|
|
2173
|
-
|
|
2237
|
+
let newGeometry = this.buildPolygonGeometry(shape);
|
|
2238
|
+
if (mesh.userData["is3D"]) newGeometry = processGeometryFor3D(newGeometry);
|
|
2239
|
+
const { vertices, indices } = countGeometry(newGeometry);
|
|
2240
|
+
if (vertices != geometryRange?.reservedVertexCount || indices != Math.max(geometryRange.reservedIndexCount, 0)) {
|
|
2241
|
+
logger$12.warn("Polygon geometry changing not supported %O", shapeDef);
|
|
2174
2242
|
return;
|
|
2175
2243
|
}
|
|
2176
|
-
|
|
2177
|
-
if (mesh.geometry.hasAttribute("normal")) geometry.computeVertexNormals();
|
|
2178
|
-
mesh.setGeometryAt(geometryId, geometry);
|
|
2244
|
+
mesh.setGeometryAt(geometryId, newGeometry);
|
|
2179
2245
|
} else if (isRect) this.updateRect(shape, mesh, instanceId);
|
|
2180
2246
|
}
|
|
2181
2247
|
updateColor(shapeDef, mesh, instanceId) {
|
|
2182
2248
|
const color = this.normalizeColor(shapeDef.color);
|
|
2183
2249
|
if (!color) {
|
|
2184
|
-
logger$
|
|
2250
|
+
logger$12.warn(`Invalid color: ${shapeDef.color} %O`, shapeDef);
|
|
2185
2251
|
return;
|
|
2186
2252
|
}
|
|
2187
2253
|
mesh.setColorAt(instanceId, this.color.setRGB(color.r / 255, color.g / 255, color.b / 255, SRGBColorSpace));
|
|
@@ -2207,10 +2273,7 @@ var MeshSystem = class extends RenderableSystem {
|
|
|
2207
2273
|
({vertices, indices} = countGeometry(rectGeom));
|
|
2208
2274
|
} else if (shapeDef.shape instanceof Polygon) {
|
|
2209
2275
|
let geometry = this.buildPolygonGeometry(shapeDef.shape);
|
|
2210
|
-
if (is3D)
|
|
2211
|
-
geometry = geometry.toNonIndexed();
|
|
2212
|
-
geometry.computeVertexNormals();
|
|
2213
|
-
}
|
|
2276
|
+
if (is3D) geometry = processGeometryFor3D(geometry);
|
|
2214
2277
|
shapeDefToGeometry.set(shapeDef, geometry);
|
|
2215
2278
|
({vertices, indices} = countGeometry(geometry));
|
|
2216
2279
|
}
|
|
@@ -2224,7 +2287,8 @@ var MeshSystem = class extends RenderableSystem {
|
|
|
2224
2287
|
});
|
|
2225
2288
|
const batchedMesh = new BatchedMesh$1(shapes.length, vertexCount, indexCount, material);
|
|
2226
2289
|
const rectGeometryId = rectAdded ? batchedMesh.addGeometry(rectGeom) : void 0;
|
|
2227
|
-
batchedMesh.setCustomSort((list) => this.sortInstances(batchedMesh, list));
|
|
2290
|
+
batchedMesh.setCustomSort((list) => this.sortInstances(batchedMesh, is3D, opacity < 1, list));
|
|
2291
|
+
if (is3D) batchedMesh.userData["is3D"] = true;
|
|
2228
2292
|
for (const shapeDef of shapes) {
|
|
2229
2293
|
let instanceId;
|
|
2230
2294
|
let type;
|
|
@@ -2254,18 +2318,316 @@ var MeshSystem = class extends RenderableSystem {
|
|
|
2254
2318
|
buildPolygonGeometry(polygon) {
|
|
2255
2319
|
return new BufferGeometry().setFromPoints(polygon.vertices).setIndex(polygon.indices.flat());
|
|
2256
2320
|
}
|
|
2257
|
-
sortInstances(mesh, list) {
|
|
2321
|
+
sortInstances(mesh, is3D, isTransparent, list) {
|
|
2258
2322
|
const shapeDefs = this.getDefsByObject(mesh);
|
|
2259
2323
|
list.sort((a, b) => {
|
|
2260
2324
|
const aDef = shapeDefs[a.index];
|
|
2261
2325
|
const bDef = shapeDefs[b.index];
|
|
2262
|
-
|
|
2326
|
+
const aDim = aDef.dim === false ? 1 : 0;
|
|
2327
|
+
const bDim = bDef.dim === false ? 1 : 0;
|
|
2328
|
+
if (aDim !== bDim) return aDim - bDim;
|
|
2329
|
+
if (!is3D) return 0;
|
|
2330
|
+
return isTransparent ? sortTransparent(a, b) : sortOpaque(a, b);
|
|
2263
2331
|
});
|
|
2264
2332
|
}
|
|
2265
2333
|
};
|
|
2334
|
+
function sortOpaque(a, b) {
|
|
2335
|
+
return a.z - b.z;
|
|
2336
|
+
}
|
|
2337
|
+
function sortTransparent(a, b) {
|
|
2338
|
+
return b.z - a.z;
|
|
2339
|
+
}
|
|
2340
|
+
function processGeometryFor3D(geometry) {
|
|
2341
|
+
let processedGeometry = geometry;
|
|
2342
|
+
processedGeometry = processedGeometry.toNonIndexed();
|
|
2343
|
+
processedGeometry.computeVertexNormals();
|
|
2344
|
+
processedGeometry = BufferGeometryUtils.toCreasedNormals(processedGeometry, Math.PI / 6);
|
|
2345
|
+
return processedGeometry;
|
|
2346
|
+
}
|
|
2347
|
+
//#endregion
|
|
2348
|
+
//#region src/geometry/model.ts
|
|
2349
|
+
var logger$11 = createLogger("model");
|
|
2350
|
+
/**
|
|
2351
|
+
* Converts the GLB Y-up coordinate system to the engine's Z-up convention.
|
|
2352
|
+
* Matches the rotation/scale applied at the scene root in {@link import("../loaders/glb.ts")}.
|
|
2353
|
+
*/
|
|
2354
|
+
var Y_UP_TO_Z_UP = new Matrix4().makeRotationX(-Math.PI / 2).multiply(new Matrix4().makeScale(1, -1, 1));
|
|
2355
|
+
/**
|
|
2356
|
+
* A system that handles the rendering of external 3D model defs (e.g. GLB/glTF).
|
|
2357
|
+
* Loads models asynchronously, builds one {@link BatchedMesh} per (ModelDef, source
|
|
2358
|
+
* material) pair so each material clone can carry its def's opacity directly, and
|
|
2359
|
+
* routes shading through {@link MaterialSystem.patchMaterial} for dim + polygon offset.
|
|
2360
|
+
*/
|
|
2361
|
+
var ModelSystem = class extends RenderableSystem {
|
|
2362
|
+
loader = new GLTFLoader();
|
|
2363
|
+
cachePromise = /* @__PURE__ */ new Map();
|
|
2364
|
+
pendingUpdates = /* @__PURE__ */ new Map();
|
|
2365
|
+
layerAborted = /* @__PURE__ */ new WeakSet();
|
|
2366
|
+
isDisposed = false;
|
|
2367
|
+
/**
|
|
2368
|
+
* @param ctx {@link RendererContext}
|
|
2369
|
+
* @param materialSystem {@link MaterialSystem}
|
|
2370
|
+
*/
|
|
2371
|
+
constructor(ctx, materialSystem) {
|
|
2372
|
+
super("model", ctx, logger$11);
|
|
2373
|
+
this.materialSystem = materialSystem;
|
|
2374
|
+
const dracoLoader = new DRACOLoader();
|
|
2375
|
+
dracoLoader.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.5.7/");
|
|
2376
|
+
this.loader.setDRACOLoader(dracoLoader);
|
|
2377
|
+
this.loader.setMeshoptDecoder(MeshoptDecoder);
|
|
2378
|
+
}
|
|
2379
|
+
buildLayer(layer) {
|
|
2380
|
+
const group = new Group();
|
|
2381
|
+
if (layer.children.length === 0) return group;
|
|
2382
|
+
this.loadAndBuild(group, layer.children);
|
|
2383
|
+
return group;
|
|
2384
|
+
}
|
|
2385
|
+
updateDef(def, firstUpdate = false) {
|
|
2386
|
+
if (!this.getObjectInstanceByDef(def)) {
|
|
2387
|
+
this.pendingUpdates.set(def, def);
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
super.updateDef(def, firstUpdate);
|
|
2391
|
+
}
|
|
2392
|
+
disposeLayer(group) {
|
|
2393
|
+
this.layerAborted.add(group);
|
|
2394
|
+
super.disposeLayer(group);
|
|
2395
|
+
}
|
|
2396
|
+
dispose() {
|
|
2397
|
+
this.isDisposed = true;
|
|
2398
|
+
super.dispose();
|
|
2399
|
+
for (const cachePromise of this.cachePromise.values()) cachePromise.then((cache) => disposeGltfCache(cache)).catch(() => void 0);
|
|
2400
|
+
this.cachePromise.clear();
|
|
2401
|
+
this.pendingUpdates.clear();
|
|
2402
|
+
}
|
|
2403
|
+
disposeObject(object) {
|
|
2404
|
+
disposeObject(object, { textures: false });
|
|
2405
|
+
}
|
|
2406
|
+
updateDefImpl(def, mesh, instanceIds) {
|
|
2407
|
+
const extents = mesh.userData["modelExtents"];
|
|
2408
|
+
if (extents) {
|
|
2409
|
+
const matrix = composeInstanceMatrix(def, extents);
|
|
2410
|
+
for (const instanceId of instanceIds) mesh.setMatrixAt(instanceId, matrix);
|
|
2411
|
+
}
|
|
2412
|
+
const material = mesh.material;
|
|
2413
|
+
const opacity = def.opacity ?? 1;
|
|
2414
|
+
material.opacity = opacity;
|
|
2415
|
+
const srcTransparent = material.userData["srcTransparent"] === true;
|
|
2416
|
+
material.transparent = opacity < 1 || srcTransparent;
|
|
2417
|
+
material.depthWrite = !material.transparent;
|
|
2418
|
+
}
|
|
2419
|
+
async loadAndBuild(group, models) {
|
|
2420
|
+
let caches;
|
|
2421
|
+
try {
|
|
2422
|
+
caches = await Promise.all(models.map((def) => this.loadCached(def.url)));
|
|
2423
|
+
} catch (err) {
|
|
2424
|
+
logger$11.warn("Failed to load model batch %O", err);
|
|
2425
|
+
return;
|
|
2426
|
+
}
|
|
2427
|
+
if (this.isDisposed || this.layerAborted.has(group)) return;
|
|
2428
|
+
const batches = [];
|
|
2429
|
+
for (let i = 0; i < models.length; i++) {
|
|
2430
|
+
const cache = caches[i];
|
|
2431
|
+
if (!cache || cache.entries.length === 0) continue;
|
|
2432
|
+
batches.push(...this.buildBatchesForDef(models[i], cache));
|
|
2433
|
+
}
|
|
2434
|
+
if (this.isDisposed || this.layerAborted.has(group)) {
|
|
2435
|
+
for (const batch of batches) {
|
|
2436
|
+
this.disposeObject(batch);
|
|
2437
|
+
this.unregisterObject(batch);
|
|
2438
|
+
}
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
group.add(...batches);
|
|
2442
|
+
for (const batch of batches) batch.renderOrder = group.renderOrder;
|
|
2443
|
+
for (const [def, latest] of this.pendingUpdates) if (this.getObjectInstanceByDef(def)) {
|
|
2444
|
+
this.pendingUpdates.delete(def);
|
|
2445
|
+
this.updateDef(latest);
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
buildBatchesForDef(def, cache) {
|
|
2449
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
2450
|
+
for (const entry of cache.entries) {
|
|
2451
|
+
const key = `${entry.sourceMaterial.uuid}|${attributeSignature(entry.geometry)}`;
|
|
2452
|
+
let bucket = buckets.get(key);
|
|
2453
|
+
if (!bucket) {
|
|
2454
|
+
bucket = {
|
|
2455
|
+
sourceMaterial: entry.sourceMaterial,
|
|
2456
|
+
geometries: []
|
|
2457
|
+
};
|
|
2458
|
+
buckets.set(key, bucket);
|
|
2459
|
+
}
|
|
2460
|
+
bucket.geometries.push(entry.geometry);
|
|
2461
|
+
}
|
|
2462
|
+
const batches = [];
|
|
2463
|
+
for (const { sourceMaterial, geometries } of buckets.values()) {
|
|
2464
|
+
const material = this.createPatchedMaterial(sourceMaterial);
|
|
2465
|
+
let totalVertices = 0;
|
|
2466
|
+
let totalIndices = 0;
|
|
2467
|
+
for (const geometry of geometries) {
|
|
2468
|
+
totalVertices += geometry.attributes["position"].count;
|
|
2469
|
+
totalIndices += geometry.index?.count ?? 0;
|
|
2470
|
+
}
|
|
2471
|
+
const batch = new BatchedMesh$1(geometries.length, totalVertices, totalIndices, material);
|
|
2472
|
+
batch.userData["modelExtents"] = cache.extents;
|
|
2473
|
+
for (const geometry of geometries) {
|
|
2474
|
+
const geometryId = batch.addGeometry(geometry);
|
|
2475
|
+
const instanceId = batch.addInstance(geometryId);
|
|
2476
|
+
this.registerDefInstance(def, batch, instanceId);
|
|
2477
|
+
}
|
|
2478
|
+
batches.push(batch);
|
|
2479
|
+
}
|
|
2480
|
+
return batches;
|
|
2481
|
+
}
|
|
2482
|
+
createPatchedMaterial(source) {
|
|
2483
|
+
const physical = new MeshPhysicalMaterial();
|
|
2484
|
+
if (source instanceof MeshStandardMaterial || source instanceof MeshPhysicalMaterial) {
|
|
2485
|
+
physical.color.copy(source.color);
|
|
2486
|
+
physical.map = source.map;
|
|
2487
|
+
physical.normalMap = source.normalMap;
|
|
2488
|
+
physical.normalScale.copy(source.normalScale);
|
|
2489
|
+
physical.roughness = source.roughness;
|
|
2490
|
+
physical.roughnessMap = source.roughnessMap;
|
|
2491
|
+
physical.metalness = source.metalness;
|
|
2492
|
+
physical.metalnessMap = source.metalnessMap;
|
|
2493
|
+
physical.emissive.copy(source.emissive);
|
|
2494
|
+
physical.emissiveIntensity = source.emissiveIntensity;
|
|
2495
|
+
physical.emissiveMap = source.emissiveMap;
|
|
2496
|
+
physical.aoMap = source.aoMap;
|
|
2497
|
+
physical.aoMapIntensity = source.aoMapIntensity;
|
|
2498
|
+
physical.alphaMap = source.alphaMap;
|
|
2499
|
+
physical.alphaTest = source.alphaTest;
|
|
2500
|
+
}
|
|
2501
|
+
physical.side = source.side;
|
|
2502
|
+
physical.transparent = source.transparent;
|
|
2503
|
+
physical.opacity = source.opacity;
|
|
2504
|
+
physical.depthWrite = !source.transparent;
|
|
2505
|
+
physical.depthFunc = source.transparent ? LessDepth : LessEqualDepth;
|
|
2506
|
+
physical.userData["srcTransparent"] = source.transparent;
|
|
2507
|
+
this.materialSystem.patchMaterial(physical, { lightingEnable: true });
|
|
2508
|
+
return physical;
|
|
2509
|
+
}
|
|
2510
|
+
async loadCached(url) {
|
|
2511
|
+
let promise = this.cachePromise.get(url);
|
|
2512
|
+
if (!promise) {
|
|
2513
|
+
promise = this.parseGltf(url);
|
|
2514
|
+
this.cachePromise.set(url, promise);
|
|
2515
|
+
}
|
|
2516
|
+
try {
|
|
2517
|
+
return await promise;
|
|
2518
|
+
} catch (err) {
|
|
2519
|
+
logger$11.warn("Failed to load model %s: %O", url, err);
|
|
2520
|
+
this.cachePromise.delete(url);
|
|
2521
|
+
return;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
async parseGltf(url) {
|
|
2525
|
+
const entries = collectGltfEntries(await this.loader.loadAsync(url));
|
|
2526
|
+
const extents = new Box3();
|
|
2527
|
+
for (const { geometry } of entries) {
|
|
2528
|
+
if (!geometry.boundingBox) geometry.computeBoundingBox();
|
|
2529
|
+
extents.union(geometry.boundingBox);
|
|
2530
|
+
}
|
|
2531
|
+
return {
|
|
2532
|
+
extents,
|
|
2533
|
+
entries
|
|
2534
|
+
};
|
|
2535
|
+
}
|
|
2536
|
+
};
|
|
2537
|
+
/**
|
|
2538
|
+
* Traverses a loaded GLTF scene and collects baked geometries paired with their
|
|
2539
|
+
* source materials. Geometries are cloned, then transformed by
|
|
2540
|
+
* {@link Y_UP_TO_Z_UP} times the node's world matrix so vertices land in engine
|
|
2541
|
+
* space relative to the GLB root.
|
|
2542
|
+
* @param gltf parsed GLTF
|
|
2543
|
+
* @returns array of `{geometry, sourceMaterial}` entries, one per Mesh node
|
|
2544
|
+
*/
|
|
2545
|
+
function collectGltfEntries(gltf) {
|
|
2546
|
+
const entries = [];
|
|
2547
|
+
gltf.scene.updateMatrixWorld(true);
|
|
2548
|
+
const tmp = new Matrix4();
|
|
2549
|
+
gltf.scene.traverse((node) => {
|
|
2550
|
+
if (!(node instanceof Mesh)) return;
|
|
2551
|
+
const mesh = node;
|
|
2552
|
+
if (!mesh.geometry || !mesh.material) return;
|
|
2553
|
+
const sourceMaterial = Array.isArray(mesh.material) ? mesh.material[0] : mesh.material;
|
|
2554
|
+
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);
|
|
2555
|
+
const baked = mesh.geometry.clone();
|
|
2556
|
+
tmp.multiplyMatrices(Y_UP_TO_Z_UP, mesh.matrixWorld);
|
|
2557
|
+
baked.applyMatrix4(tmp);
|
|
2558
|
+
baked.computeBoundingBox();
|
|
2559
|
+
baked.computeBoundingSphere();
|
|
2560
|
+
entries.push({
|
|
2561
|
+
geometry: baked,
|
|
2562
|
+
sourceMaterial
|
|
2563
|
+
});
|
|
2564
|
+
});
|
|
2565
|
+
return entries;
|
|
2566
|
+
}
|
|
2567
|
+
/**
|
|
2568
|
+
* Disposes a cached GLB's baked geometries and the unique textures referenced by its
|
|
2569
|
+
* source materials. Source textures are shared across batches, so this is the only
|
|
2570
|
+
* place they should be released.
|
|
2571
|
+
* @param cache cache entry to dispose
|
|
2572
|
+
*/
|
|
2573
|
+
function disposeGltfCache(cache) {
|
|
2574
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2575
|
+
const textureProps = [
|
|
2576
|
+
"map",
|
|
2577
|
+
"normalMap",
|
|
2578
|
+
"roughnessMap",
|
|
2579
|
+
"metalnessMap",
|
|
2580
|
+
"emissiveMap",
|
|
2581
|
+
"aoMap",
|
|
2582
|
+
"alphaMap"
|
|
2583
|
+
];
|
|
2584
|
+
for (const { sourceMaterial } of cache.entries) {
|
|
2585
|
+
const matRecord = sourceMaterial;
|
|
2586
|
+
for (const prop of textureProps) {
|
|
2587
|
+
const tex = matRecord[prop];
|
|
2588
|
+
if (tex && tex instanceof Texture && !seen.has(tex)) {
|
|
2589
|
+
seen.add(tex);
|
|
2590
|
+
tex.dispose();
|
|
2591
|
+
}
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
for (const { geometry } of cache.entries) geometry.dispose();
|
|
2595
|
+
}
|
|
2596
|
+
/**
|
|
2597
|
+
* Stable identifier for a geometry's attribute set. Used to sub-bucket geometries that
|
|
2598
|
+
* share a source material but have different attribute names (e.g. with vs without uv),
|
|
2599
|
+
* since {@link BatchedMesh.addGeometry} requires homogeneous attributes per batch.
|
|
2600
|
+
* @param geometry geometry to summarize
|
|
2601
|
+
* @returns comma-separated sorted attribute names
|
|
2602
|
+
*/
|
|
2603
|
+
function attributeSignature(geometry) {
|
|
2604
|
+
return Object.keys(geometry.attributes).sort().join(",");
|
|
2605
|
+
}
|
|
2606
|
+
/**
|
|
2607
|
+
* Composes a per-instance world matrix from a ModelDef's bounds and the loaded GLB's
|
|
2608
|
+
* engine-space extents: scale uniformly to fit bounds.size in XY, rotate around Z by
|
|
2609
|
+
* bounds.rotation, and translate so the model sits at bounds.center with its base at
|
|
2610
|
+
* bounds.elevation.
|
|
2611
|
+
* @param def model definition
|
|
2612
|
+
* @param extents engine-space bounding box of the loaded GLB
|
|
2613
|
+
* @returns world matrix to set on a BatchedMesh instance
|
|
2614
|
+
*/
|
|
2615
|
+
function composeInstanceMatrix(def, extents) {
|
|
2616
|
+
const size = extents.getSize(tempSize);
|
|
2617
|
+
const center = extents.getCenter(tempCenter);
|
|
2618
|
+
const sx = size.x > 0 ? def.bounds.size.x / size.x : 1;
|
|
2619
|
+
const sy = size.y > 0 ? def.bounds.size.y / size.y : 1;
|
|
2620
|
+
const scale = Math.min(sx, sy);
|
|
2621
|
+
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));
|
|
2622
|
+
}
|
|
2623
|
+
var tempSize = new Vector3();
|
|
2624
|
+
var tempCenter = new Vector3();
|
|
2625
|
+
var tempScale = new Matrix4();
|
|
2626
|
+
var tempRotation = new Matrix4();
|
|
2627
|
+
var tempTranslation = new Matrix4();
|
|
2266
2628
|
//#endregion
|
|
2267
2629
|
//#region src/geometry/text.ts
|
|
2268
|
-
var logger$
|
|
2630
|
+
var logger$10 = createLogger("text");
|
|
2269
2631
|
/**
|
|
2270
2632
|
* A system that handles the rendering of text defs.
|
|
2271
2633
|
*/
|
|
@@ -2286,7 +2648,7 @@ var TextSystem = class extends RenderableSystem {
|
|
|
2286
2648
|
* @param materialSystem {@link MaterialSystem}
|
|
2287
2649
|
*/
|
|
2288
2650
|
constructor(ctx, materialSystem) {
|
|
2289
|
-
super("text", ctx, logger$
|
|
2651
|
+
super("text", ctx, logger$10);
|
|
2290
2652
|
this.materialSystem = materialSystem;
|
|
2291
2653
|
}
|
|
2292
2654
|
dispose() {
|
|
@@ -2364,6 +2726,9 @@ var TextSystem = class extends RenderableSystem {
|
|
|
2364
2726
|
const textDefs = layer.children;
|
|
2365
2727
|
const batchedText = new BatchedText$1();
|
|
2366
2728
|
batchedText.material = this.materialSystem.createColorMaterial({ depthEnable: is3D });
|
|
2729
|
+
batchedText.material.transparent = false;
|
|
2730
|
+
batchedText.material.blending = CustomBlending;
|
|
2731
|
+
batchedText.material.blendSrcAlpha = OneFactor;
|
|
2367
2732
|
const mappingData = [];
|
|
2368
2733
|
let instanceId = 0;
|
|
2369
2734
|
for (const textDef of textDefs) {
|
|
@@ -2458,7 +2823,7 @@ function setAnchorsAndAlignment(text, alignment) {
|
|
|
2458
2823
|
}
|
|
2459
2824
|
//#endregion
|
|
2460
2825
|
//#region src/geometry/layer.ts
|
|
2461
|
-
var logger$
|
|
2826
|
+
var logger$9 = createLogger("layer");
|
|
2462
2827
|
/**
|
|
2463
2828
|
* A system that handles the rendering of layer defs and scene graph building.
|
|
2464
2829
|
*/
|
|
@@ -2469,6 +2834,7 @@ var LayerSystem = class {
|
|
|
2469
2834
|
imageSystem;
|
|
2470
2835
|
textSystem;
|
|
2471
2836
|
lineSystem;
|
|
2837
|
+
modelSystem;
|
|
2472
2838
|
systems;
|
|
2473
2839
|
mapLayerDefsToObjects = /* @__PURE__ */ new Map();
|
|
2474
2840
|
mapLayerDefToParent = /* @__PURE__ */ new Map();
|
|
@@ -2483,11 +2849,13 @@ var LayerSystem = class {
|
|
|
2483
2849
|
this.imageSystem = new ImageSystem(this.ctx, this.materialSystem);
|
|
2484
2850
|
this.textSystem = new TextSystem(this.ctx, this.materialSystem);
|
|
2485
2851
|
this.lineSystem = new LineSystem(this.ctx, this.materialSystem);
|
|
2852
|
+
this.modelSystem = new ModelSystem(this.ctx, this.materialSystem);
|
|
2486
2853
|
this.systems = [
|
|
2487
2854
|
this.meshSystem,
|
|
2488
2855
|
this.imageSystem,
|
|
2489
2856
|
this.textSystem,
|
|
2490
|
-
this.lineSystem
|
|
2857
|
+
this.lineSystem,
|
|
2858
|
+
this.modelSystem
|
|
2491
2859
|
];
|
|
2492
2860
|
}
|
|
2493
2861
|
/**
|
|
@@ -2513,14 +2881,14 @@ var LayerSystem = class {
|
|
|
2513
2881
|
*/
|
|
2514
2882
|
buildScene(sceneDef) {
|
|
2515
2883
|
const renderOrder = this.initRenderOrder(sceneDef.rootLayer).map((layer) => this.getFullLayerName(layer));
|
|
2516
|
-
logger$
|
|
2884
|
+
logger$9.debug("Render order %O", renderOrder);
|
|
2517
2885
|
const rootGroup = new Group();
|
|
2518
2886
|
rootGroup.name = sceneDef.rootLayer.name;
|
|
2519
2887
|
this.mapLayerDefsToObjects.set(sceneDef.rootLayer, rootGroup);
|
|
2520
2888
|
if (sceneDef.background) rootGroup.add(this.createBackgroundLayer(sceneDef.background));
|
|
2521
2889
|
for (const child of sceneDef.rootLayer.children) rootGroup.add(this.buildLayer(child));
|
|
2522
2890
|
this.updateLayer(sceneDef.rootLayer, false);
|
|
2523
|
-
printTree(rootGroup, logger$
|
|
2891
|
+
printTree(rootGroup, logger$9.debug);
|
|
2524
2892
|
if (sceneDef.memoryLimit) this.imageSystem.memoryLimitMb = sceneDef.memoryLimit;
|
|
2525
2893
|
this.imageSystem.renderAtlases();
|
|
2526
2894
|
return rootGroup;
|
|
@@ -2553,6 +2921,7 @@ var LayerSystem = class {
|
|
|
2553
2921
|
else if (isImageDef(def)) this.imageSystem.updateDef(def);
|
|
2554
2922
|
else if (isTextDef(def)) this.textSystem.updateDef(def);
|
|
2555
2923
|
else if (isLineDef(def)) this.lineSystem.updateDef(def);
|
|
2924
|
+
else if (isModelDef(def)) this.modelSystem.updateDef(def);
|
|
2556
2925
|
else this.updateLayer(def, true);
|
|
2557
2926
|
}
|
|
2558
2927
|
disposeLayerTree(layerDef) {
|
|
@@ -2563,6 +2932,7 @@ var LayerSystem = class {
|
|
|
2563
2932
|
else if (isImageLayer(layerDef)) this.imageSystem.disposeLayer(layerObject);
|
|
2564
2933
|
else if (isTextLayer(layerDef)) this.textSystem.disposeLayer(layerObject);
|
|
2565
2934
|
else if (isLineLayer(layerDef)) this.lineSystem.disposeLayer(layerObject);
|
|
2935
|
+
else if (isModelLayer(layerDef)) this.modelSystem.disposeLayer(layerObject);
|
|
2566
2936
|
this.mapLayerDefsToObjects.delete(layerDef);
|
|
2567
2937
|
this.mapLayerDefToParent.delete(layerDef);
|
|
2568
2938
|
this.renderOrderMap.delete(layerDef);
|
|
@@ -2576,6 +2946,7 @@ var LayerSystem = class {
|
|
|
2576
2946
|
else if (isLineLayer(layerDef)) this.lineSystem.updateLayer(group, layerDef);
|
|
2577
2947
|
else if (isTextLayer(layerDef)) this.textSystem.updateLayer(group, layerDef);
|
|
2578
2948
|
else if (isShapeLayer(layerDef)) this.meshSystem.updateLayer(group, layerDef);
|
|
2949
|
+
else if (isModelLayer(layerDef)) this.modelSystem.updateLayer(group, layerDef);
|
|
2579
2950
|
this.setLayerName(group, group.name);
|
|
2580
2951
|
}
|
|
2581
2952
|
layerObject.visible = !layerDef.hidden && layerDef.children.length > 0;
|
|
@@ -2585,12 +2956,13 @@ var LayerSystem = class {
|
|
|
2585
2956
|
}
|
|
2586
2957
|
buildLayer(layerDef, parentPrefix = "") {
|
|
2587
2958
|
const layerFullName = parentPrefix + layerDef.name;
|
|
2588
|
-
logger$
|
|
2959
|
+
logger$9.debug(`Building layer ${layerFullName}...`);
|
|
2589
2960
|
let layerObject;
|
|
2590
2961
|
if (isShapeLayer(layerDef)) layerObject = this.meshSystem.buildLayer(layerDef);
|
|
2591
2962
|
else if (isImageLayer(layerDef)) layerObject = this.imageSystem.buildLayer(layerDef);
|
|
2592
2963
|
else if (isTextLayer(layerDef)) layerObject = this.textSystem.buildLayer(layerDef);
|
|
2593
2964
|
else if (isLineLayer(layerDef)) layerObject = this.lineSystem.buildLayer(layerDef);
|
|
2965
|
+
else if (isModelLayer(layerDef)) layerObject = this.modelSystem.buildLayer(layerDef);
|
|
2594
2966
|
else {
|
|
2595
2967
|
layerObject = new Group();
|
|
2596
2968
|
layerDef.children.map((layer) => this.buildLayer(layer, parentPrefix + `${layerDef.name}:`)).forEach((g) => layerObject.add(g));
|
|
@@ -2610,11 +2982,8 @@ var LayerSystem = class {
|
|
|
2610
2982
|
setRenderOrder(object, layer) {
|
|
2611
2983
|
const renderOrder = this.renderOrderMap.get(layer);
|
|
2612
2984
|
if (renderOrder === void 0) return;
|
|
2613
|
-
if (object.isGroup) {
|
|
2614
|
-
object.children.forEach((child) => this.setRenderOrder(child, layer));
|
|
2615
|
-
return;
|
|
2616
|
-
}
|
|
2617
2985
|
object.renderOrder = renderOrder;
|
|
2986
|
+
if (object.isGroup) object.children.forEach((child) => this.setRenderOrder(child, layer));
|
|
2618
2987
|
}
|
|
2619
2988
|
createBackgroundLayer(color) {
|
|
2620
2989
|
const backgroundMesh = new Mesh(new PlaneGeometry(2, 2), this.materialSystem.createBackgroundMaterial(color));
|
|
@@ -2670,219 +3039,154 @@ var LayerSystem = class {
|
|
|
2670
3039
|
//#region src/geometry/lights.ts
|
|
2671
3040
|
/** System for managing directional lights in 3D scenes. */
|
|
2672
3041
|
var LightsSystem = class {
|
|
2673
|
-
|
|
2674
|
-
|
|
3042
|
+
skyColor = 16777215;
|
|
3043
|
+
groundColor = 0;
|
|
3044
|
+
intensity = Math.PI;
|
|
2675
3045
|
mapSceneToLight = /* @__PURE__ */ new Map();
|
|
3046
|
+
pmremGenerator;
|
|
3047
|
+
/**
|
|
3048
|
+
* @param ctx {@link RendererContext} instance
|
|
3049
|
+
*/
|
|
3050
|
+
constructor(ctx) {
|
|
3051
|
+
this.ctx = ctx;
|
|
3052
|
+
this.pmremGenerator = new PMREMGenerator(this.ctx.three);
|
|
3053
|
+
}
|
|
2676
3054
|
/**
|
|
2677
3055
|
* Initializes a directional light for the given scene.
|
|
2678
3056
|
* @param scene {@link Scene} to add the light to
|
|
2679
3057
|
*/
|
|
2680
3058
|
initLights(scene) {
|
|
2681
|
-
const
|
|
2682
|
-
|
|
2683
|
-
const
|
|
3059
|
+
const hemisphereLightIntensity = this.intensity * .25;
|
|
3060
|
+
const hemisphereLight = new HemisphereLight(this.skyColor, this.groundColor, hemisphereLightIntensity);
|
|
3061
|
+
const matrixWorldInverse = new Matrix4().copy(scene.matrixWorld).invert();
|
|
3062
|
+
const position = new Vector3(0, 0, -1).applyMatrix4(matrixWorldInverse);
|
|
3063
|
+
hemisphereLight.position.copy(position);
|
|
3064
|
+
scene.add(hemisphereLight);
|
|
3065
|
+
const directionalLightIntensity = this.intensity * .5;
|
|
3066
|
+
const directionalLight = new DirectionalLight(this.skyColor, directionalLightIntensity);
|
|
2684
3067
|
scene.add(directionalLight);
|
|
2685
3068
|
scene.add(directionalLight.target);
|
|
2686
3069
|
this.mapSceneToLight.set(scene, directionalLight);
|
|
3070
|
+
scene.environment = this.pmremGenerator.fromScene(new ColorEnvironment(), .04).texture;
|
|
3071
|
+
scene.environmentIntensity = .25;
|
|
2687
3072
|
}
|
|
2688
3073
|
/**
|
|
2689
3074
|
* Updates the light direction to match the camera's viewing direction.
|
|
2690
|
-
* @param
|
|
3075
|
+
* @param sceneState {@link SceneState} containing the light
|
|
2691
3076
|
* @param camera {@link Camera} to derive the light direction from
|
|
2692
3077
|
*/
|
|
2693
|
-
updateLights(
|
|
2694
|
-
const light = this.mapSceneToLight.get(scene);
|
|
3078
|
+
updateLights(sceneState, camera) {
|
|
3079
|
+
const light = this.mapSceneToLight.get(sceneState.scene);
|
|
2695
3080
|
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();
|
|
3081
|
+
camera.getWorldDirection(light.position).transformDirection(sceneState.worldMatrixInverse).negate();
|
|
2702
3082
|
}
|
|
2703
3083
|
};
|
|
2704
3084
|
//#endregion
|
|
2705
|
-
//#region src/
|
|
2706
|
-
|
|
2707
|
-
var logger$7 = createLogger("");
|
|
3085
|
+
//#region src/loaders/glb.ts
|
|
3086
|
+
var logger$8 = createLogger("glb");
|
|
2708
3087
|
/**
|
|
2709
|
-
*
|
|
2710
|
-
* @param
|
|
2711
|
-
* @
|
|
2712
|
-
* @returns false if disposed, true otherwise
|
|
3088
|
+
* Loads a GLB file and returns a Three.js model.
|
|
3089
|
+
* @param models - The models to load.
|
|
3090
|
+
* @returns An object containing parsed Three.js objects.
|
|
2713
3091
|
*/
|
|
2714
|
-
function
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
3092
|
+
async function loadGLB(models) {
|
|
3093
|
+
const loader = new GLTFLoader();
|
|
3094
|
+
const dracoLoader = new DRACOLoader();
|
|
3095
|
+
dracoLoader.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.5.7/");
|
|
3096
|
+
const root = new Group();
|
|
3097
|
+
for (const model of models) {
|
|
3098
|
+
if (model.isMeshopt) loader.setMeshoptDecoder(MeshoptDecoder);
|
|
3099
|
+
if (model.isDraco) loader.setDRACOLoader(dracoLoader);
|
|
3100
|
+
const loadedModel = await loader.loadAsync(model.url);
|
|
3101
|
+
logger$8.info("model loaded", loadedModel);
|
|
3102
|
+
root.add(...loadedModel.scene.children);
|
|
3103
|
+
loader.setMeshoptDecoder(null);
|
|
3104
|
+
loader.setDRACOLoader(null);
|
|
3105
|
+
}
|
|
3106
|
+
const bounds = new Box3();
|
|
3107
|
+
root.traverse((node) => {
|
|
3108
|
+
if (node.userData && Object.keys(node.userData).filter((key) => key !== "name").length > 0) logger$8.info(`node ${node.name} userData:`, node.userData);
|
|
3109
|
+
if (node instanceof Mesh && node.geometry) {
|
|
3110
|
+
const materialJSON = node.material.toJSON();
|
|
3111
|
+
logger$8.info(`node ${node.name} material %O:`, materialJSON);
|
|
3112
|
+
const geometry = node.geometry;
|
|
3113
|
+
if (!geometry.boundingBox) geometry.computeBoundingBox();
|
|
3114
|
+
const box = geometry.boundingBox.clone();
|
|
3115
|
+
box.applyMatrix4(node.matrixWorld);
|
|
3116
|
+
bounds.union(box);
|
|
3117
|
+
}
|
|
3118
|
+
});
|
|
3119
|
+
logger$8.info("model bounds", bounds);
|
|
3120
|
+
root.rotation.x = -Math.PI / 2;
|
|
3121
|
+
root.scale.set(1, -1, 1);
|
|
3122
|
+
return {
|
|
3123
|
+
sceneDef: {
|
|
3124
|
+
rootLayer: {
|
|
3125
|
+
children: [],
|
|
3126
|
+
name: "root"
|
|
3127
|
+
},
|
|
3128
|
+
viewbox: new Rect([bounds.min.x, bounds.min.z], [bounds.max.x, bounds.max.z])
|
|
3129
|
+
},
|
|
3130
|
+
root
|
|
3131
|
+
};
|
|
2733
3132
|
}
|
|
2734
3133
|
/**
|
|
2735
|
-
*
|
|
2736
|
-
*
|
|
2737
|
-
* @param
|
|
2738
|
-
* @
|
|
3134
|
+
* Loads a GLB file, converts it to a Three.js {@link Scene}, exports the scene as a GLTF
|
|
3135
|
+
* (JSON) file, and triggers a browser download of the result.
|
|
3136
|
+
* @param url - The URL of the GLB file.
|
|
3137
|
+
* @param isMeshopt - Whether the model file uses meshopt compression.
|
|
2739
3138
|
*/
|
|
2740
|
-
function
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
3139
|
+
async function glb2gltf(url, isMeshopt) {
|
|
3140
|
+
const loader = new GLTFLoader();
|
|
3141
|
+
if (isMeshopt) loader.setMeshoptDecoder(MeshoptDecoder);
|
|
3142
|
+
const model = await loader.loadAsync(url);
|
|
3143
|
+
logger$8.info("glb loaded for export", model);
|
|
3144
|
+
const scene = new Scene();
|
|
3145
|
+
for (const child of model.scene.children) scene.add(child.clone());
|
|
3146
|
+
const gltf = await new GLTFExporter().parseAsync(scene);
|
|
3147
|
+
logger$8.info("scene exported as gltf");
|
|
3148
|
+
const filename = `${deriveBasename(url)}.gltf`;
|
|
3149
|
+
saveBlob(new Blob([JSON.stringify(gltf, null, 2)], { type: "application/json" }), filename);
|
|
2746
3150
|
}
|
|
2747
3151
|
/**
|
|
2748
|
-
*
|
|
2749
|
-
*
|
|
2750
|
-
*
|
|
2751
|
-
*
|
|
3152
|
+
* Loads a GLTF/GLB file and splits its top-level children into two GLB files based on
|
|
3153
|
+
* whether each child's name starts with `BasisTransform`. The matching children are saved
|
|
3154
|
+
* with a `-decor` suffix; the rest are saved with a `-base` suffix. Both files are
|
|
3155
|
+
* triggered as browser downloads. The output is intentionally uncompressed — run a
|
|
3156
|
+
* meshopt/quantize pass offline (e.g. via `gltf-transform`) afterwards.
|
|
3157
|
+
* @param url - The URL of the GLTF/GLB file.
|
|
2752
3158
|
*/
|
|
2753
|
-
function
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
3159
|
+
async function splitGltf(url) {
|
|
3160
|
+
const model = await new GLTFLoader().loadAsync(url);
|
|
3161
|
+
logger$8.info("gltf loaded for split", model);
|
|
3162
|
+
const baseScene = new Scene();
|
|
3163
|
+
const decorScene = new Scene();
|
|
3164
|
+
for (const child of model.scene.children) (child.name.startsWith("BasisTransform") ? decorScene : baseScene).add(child.clone());
|
|
3165
|
+
logger$8.info("split: base=%d decor=%d", baseScene.children.length, decorScene.children.length);
|
|
3166
|
+
const exporter = new GLTFExporter();
|
|
3167
|
+
const baseGlb = await exporter.parseAsync(baseScene, { binary: true });
|
|
3168
|
+
const decorGlb = await exporter.parseAsync(decorScene, { binary: true });
|
|
3169
|
+
const stem = deriveBasename(url);
|
|
3170
|
+
saveBlob(new Blob([baseGlb], { type: "model/gltf-binary" }), `${stem}-base.glb`);
|
|
3171
|
+
saveBlob(new Blob([decorGlb], { type: "model/gltf-binary" }), `${stem}-decor.glb`);
|
|
2759
3172
|
}
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
* @param matrix number array to check
|
|
2763
|
-
* @param name name of the matrix for error messages
|
|
2764
|
-
* @param log optional logger instance to use for warning messages (defaults to root logger)
|
|
2765
|
-
* @returns true if matrix is valid, false otherwise
|
|
2766
|
-
*/
|
|
2767
|
-
function assertValidMatrix(matrix, name, log = logger$7) {
|
|
2768
|
-
if (matrix.length !== 16) {
|
|
2769
|
-
log.warn(`${name}: Matrix must be 16 elements long`);
|
|
2770
|
-
return false;
|
|
2771
|
-
}
|
|
2772
|
-
return true;
|
|
3173
|
+
function deriveBasename(url) {
|
|
3174
|
+
return (url.split("?")[0].split("#")[0].split("/").pop() ?? "model").replace(/\.(glb|gltf)$/i, "");
|
|
2773
3175
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
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;
|
|
2797
|
-
}
|
|
2798
|
-
//#endregion
|
|
2799
|
-
//#region src/space/external.ts
|
|
2800
|
-
var externalLogger = createLogger("external");
|
|
2801
|
-
/**
|
|
2802
|
-
* Camera system in external mode. Exposes dummy camera instance as a wrapper for external camera transforms,
|
|
2803
|
-
* and estimates zoom factor ndc -> world conversion.
|
|
2804
|
-
*/
|
|
2805
|
-
var ExternalCameraSystem = class {
|
|
2806
|
-
/** External camera instance */
|
|
2807
|
-
camera = new Camera();
|
|
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
|
-
});
|
|
3176
|
+
function saveBlob(blob, filename) {
|
|
3177
|
+
const objectUrl = URL.createObjectURL(blob);
|
|
3178
|
+
const link = document.createElement("a");
|
|
3179
|
+
link.href = objectUrl;
|
|
3180
|
+
link.download = filename;
|
|
3181
|
+
link.style.display = "none";
|
|
3182
|
+
document.body.appendChild(link);
|
|
3183
|
+
link.click();
|
|
3184
|
+
document.body.removeChild(link);
|
|
3185
|
+
URL.revokeObjectURL(objectUrl);
|
|
2882
3186
|
}
|
|
2883
3187
|
//#endregion
|
|
2884
3188
|
//#region src/ui/controls.ts
|
|
2885
|
-
var logger$
|
|
3189
|
+
var logger$7 = createLogger("controls");
|
|
2886
3190
|
/** Navigation system. Manages camera controls and zooming. */
|
|
2887
3191
|
var ControlsSystem = class {
|
|
2888
3192
|
controller;
|
|
@@ -2926,7 +3230,7 @@ var ControlsSystem = class {
|
|
|
2926
3230
|
const worldRect = new Rect(this.coordinatesSystem.modelToWorld(rect.min), this.coordinatesSystem.modelToWorld(rect.max));
|
|
2927
3231
|
const sourceRect = Polygon.fromRect(worldRect).rotate(bearingAngle).bounds;
|
|
2928
3232
|
if (sourceRect.size.x <= 0 || sourceRect.size.y <= 0) {
|
|
2929
|
-
logger$
|
|
3233
|
+
logger$7.warn("zoomTo: sourceRect size is 0");
|
|
2930
3234
|
return;
|
|
2931
3235
|
}
|
|
2932
3236
|
if (paddingPercent) targetRect.expand({
|
|
@@ -3225,7 +3529,7 @@ function asEventAPI(system) {
|
|
|
3225
3529
|
}
|
|
3226
3530
|
//#endregion
|
|
3227
3531
|
//#region src/space/coordinates.ts
|
|
3228
|
-
var logger$
|
|
3532
|
+
var logger$6 = createLogger("coordinates");
|
|
3229
3533
|
/** System responsible for converting coordinates between different spaces. */
|
|
3230
3534
|
var CoordinatesSystem = class {
|
|
3231
3535
|
/** Used as a scratch vector for coordinate space conversions */
|
|
@@ -3298,7 +3602,7 @@ var CoordinatesSystem = class {
|
|
|
3298
3602
|
canvasToNDC(point, out = new Vector2()) {
|
|
3299
3603
|
const [w, h] = this.ctx.getDrawingBufferSizePx();
|
|
3300
3604
|
if (w <= 0 || h <= 0) {
|
|
3301
|
-
logger$
|
|
3605
|
+
logger$6.warn("canvasToNDC: renderer size is 0");
|
|
3302
3606
|
return out.set(0, 0);
|
|
3303
3607
|
}
|
|
3304
3608
|
const dpr = this.ctx.three.getPixelRatio();
|
|
@@ -3466,7 +3770,7 @@ function createNoopHandler() {
|
|
|
3466
3770
|
};
|
|
3467
3771
|
}
|
|
3468
3772
|
//#endregion
|
|
3469
|
-
//#region ../../node_modules/.pnpm/camera-controls@3.1.1_patch_hash=1d30d1431514ed87b48b2ec3defd4fe64eca4cb9b29373199021281dbc5d7782_three@0.
|
|
3773
|
+
//#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
3774
|
/*!
|
|
3471
3775
|
* camera-controls
|
|
3472
3776
|
* https://github.com/yomotsu/camera-controls
|
|
@@ -5838,7 +6142,7 @@ var subsetOfTHREE = {
|
|
|
5838
6142
|
Plane
|
|
5839
6143
|
};
|
|
5840
6144
|
CameraControls.install({ THREE: subsetOfTHREE });
|
|
5841
|
-
var logger$
|
|
6145
|
+
var logger$5 = createLogger("cameraController");
|
|
5842
6146
|
/**
|
|
5843
6147
|
* Minimum z-axis separation between overlapping surfaces, in SVG/model units.
|
|
5844
6148
|
*
|
|
@@ -5912,7 +6216,7 @@ var CameraController = class CameraController extends CameraControls {
|
|
|
5912
6216
|
radius
|
|
5913
6217
|
].map((value) => +value.toFixed(2));
|
|
5914
6218
|
const clippingPlanes = [this.camera.near, this.camera.far];
|
|
5915
|
-
logger$
|
|
6219
|
+
logger$5.debug("camera update %O", {
|
|
5916
6220
|
position,
|
|
5917
6221
|
target,
|
|
5918
6222
|
spherical,
|
|
@@ -6567,6 +6871,100 @@ var noopHandlers = {
|
|
|
6567
6871
|
pitch: createNoopHandler()
|
|
6568
6872
|
};
|
|
6569
6873
|
//#endregion
|
|
6874
|
+
//#region src/util/asserts.ts
|
|
6875
|
+
/** Logger instance for assert warnings */
|
|
6876
|
+
var logger$4 = createLogger("");
|
|
6877
|
+
/**
|
|
6878
|
+
* Asserts the renderer has not been disposed.
|
|
6879
|
+
* @param renderer - Renderer instance to check
|
|
6880
|
+
* @param funcName - Name of the calling function for error messages
|
|
6881
|
+
* @returns false if disposed, true otherwise
|
|
6882
|
+
*/
|
|
6883
|
+
function assertNotDisposed(renderer, funcName) {
|
|
6884
|
+
if (renderer.isDisposed) {
|
|
6885
|
+
logger$4.warn(`[${funcName}]: Renderer is used after being disposed. Please create a new instance.`);
|
|
6886
|
+
return false;
|
|
6887
|
+
}
|
|
6888
|
+
return true;
|
|
6889
|
+
}
|
|
6890
|
+
/**
|
|
6891
|
+
* Asserts the renderer has been initialized.
|
|
6892
|
+
* @param renderer - Renderer instance to check
|
|
6893
|
+
* @param funcName - Name of the calling function for error messages
|
|
6894
|
+
* @returns false if not initialized, true otherwise
|
|
6895
|
+
*/
|
|
6896
|
+
function assertInitialized(renderer, funcName) {
|
|
6897
|
+
if (!renderer.isInitialized) {
|
|
6898
|
+
logger$4.warn(`${funcName}: Renderer is not initialized. Please call init() before using it.`);
|
|
6899
|
+
return false;
|
|
6900
|
+
}
|
|
6901
|
+
return true;
|
|
6902
|
+
}
|
|
6903
|
+
/**
|
|
6904
|
+
* Asserts the renderer has not been initialized yet.
|
|
6905
|
+
* @param renderer - Renderer instance to check
|
|
6906
|
+
* @param funcName - Name of the calling function for error messages
|
|
6907
|
+
* @returns false if already initialized, true otherwise
|
|
6908
|
+
*/
|
|
6909
|
+
function assertNotInitialized(renderer, funcName) {
|
|
6910
|
+
if (renderer.isInitialized) {
|
|
6911
|
+
logger$4.warn(`${funcName}: Renderer is already initialized. Please call init() only once.`);
|
|
6912
|
+
return false;
|
|
6913
|
+
}
|
|
6914
|
+
return true;
|
|
6915
|
+
}
|
|
6916
|
+
/**
|
|
6917
|
+
* Asserts the renderer is not in external mode.
|
|
6918
|
+
* @param renderer - Renderer instance to check
|
|
6919
|
+
* @param funcName - Name of the calling function for error messages
|
|
6920
|
+
* @returns false if in external mode, true otherwise
|
|
6921
|
+
*/
|
|
6922
|
+
function assertNotExternalMode(renderer, funcName) {
|
|
6923
|
+
if (renderer.isExternalMode) {
|
|
6924
|
+
logger$4.warn(`${funcName}: This operation is not supported in external mode.`);
|
|
6925
|
+
return false;
|
|
6926
|
+
}
|
|
6927
|
+
return true;
|
|
6928
|
+
}
|
|
6929
|
+
/**
|
|
6930
|
+
* Asserts that number array is a valid 4x4 matrix.
|
|
6931
|
+
* @param matrix number array to check
|
|
6932
|
+
* @param name name of the matrix for error messages
|
|
6933
|
+
* @param log optional logger instance to use for warning messages (defaults to root logger)
|
|
6934
|
+
* @returns true if matrix is valid, false otherwise
|
|
6935
|
+
*/
|
|
6936
|
+
function assertValidMatrix(matrix, name, log = logger$4) {
|
|
6937
|
+
if (matrix.length !== 16) {
|
|
6938
|
+
log.warn(`${name}: Matrix must be 16 elements long`);
|
|
6939
|
+
return false;
|
|
6940
|
+
}
|
|
6941
|
+
return true;
|
|
6942
|
+
}
|
|
6943
|
+
/**
|
|
6944
|
+
* Wraps an API object so that every method is guarded by a precondition check.
|
|
6945
|
+
* When the guard (or a per-method override) fails, the corresponding fallback
|
|
6946
|
+
* method is called instead of the real implementation.
|
|
6947
|
+
* Non-function properties are passed through from `impl` (if present) or `fallback`.
|
|
6948
|
+
* @param impl - Real implementation, or null when the backing system doesn't exist
|
|
6949
|
+
* @param fallback - Noop / default implementation used when guard fails or impl is null
|
|
6950
|
+
* @param guard - Default guard applied to every method
|
|
6951
|
+
* @param guards - Optional per-method guard overrides (takes precedence over `guard`)
|
|
6952
|
+
* @returns A new object conforming to T with every method wrapped
|
|
6953
|
+
*/
|
|
6954
|
+
function guardAPI(impl, fallback, guard, guards) {
|
|
6955
|
+
const guarded = {};
|
|
6956
|
+
for (const [key, fallbackVal] of Object.entries(fallback)) if (typeof fallbackVal === "function") {
|
|
6957
|
+
const implFn = impl?.[key];
|
|
6958
|
+
const methodGuard = guards?.[key] ?? guard;
|
|
6959
|
+
const fallbackFn = fallbackVal;
|
|
6960
|
+
guarded[key] = (...args) => {
|
|
6961
|
+
if (methodGuard(key) && implFn) return implFn(...args);
|
|
6962
|
+
return fallbackFn(...args);
|
|
6963
|
+
};
|
|
6964
|
+
} else guarded[key] = impl?.[key] ?? fallbackVal;
|
|
6965
|
+
return guarded;
|
|
6966
|
+
}
|
|
6967
|
+
//#endregion
|
|
6570
6968
|
//#region src/core/updates.ts
|
|
6571
6969
|
var logger$3 = createLogger("updates");
|
|
6572
6970
|
/**
|
|
@@ -6752,6 +7150,85 @@ var InternalCameraSystem = class {
|
|
|
6752
7150
|
}
|
|
6753
7151
|
};
|
|
6754
7152
|
//#endregion
|
|
7153
|
+
//#region src/space/external.ts
|
|
7154
|
+
var externalLogger = createLogger("external");
|
|
7155
|
+
/**
|
|
7156
|
+
* Camera system in external mode. Exposes dummy camera instance as a wrapper for external camera transforms,
|
|
7157
|
+
* and estimates zoom factor ndc -> world conversion.
|
|
7158
|
+
*/
|
|
7159
|
+
var ExternalCameraSystem = class {
|
|
7160
|
+
/** External camera instance */
|
|
7161
|
+
camera = new Camera();
|
|
7162
|
+
ndcLeft = new Vector2(-1, 0);
|
|
7163
|
+
ndcRight = new Vector2(1, 0);
|
|
7164
|
+
intersectionPointLeft = new Vector3();
|
|
7165
|
+
intersectionPointRight = new Vector3();
|
|
7166
|
+
prevZoomFactor;
|
|
7167
|
+
mainMatrix4 = new Matrix4();
|
|
7168
|
+
mainMatrixInverse = new Matrix4();
|
|
7169
|
+
nearPoint = new Vector3();
|
|
7170
|
+
farPoint = new Vector3();
|
|
7171
|
+
viewDir = new Vector3();
|
|
7172
|
+
target = new Vector3();
|
|
7173
|
+
up = new Vector3();
|
|
7174
|
+
cameraPos = new Vector3();
|
|
7175
|
+
linearSystem = new Matrix3();
|
|
7176
|
+
rhs = new Vector3();
|
|
7177
|
+
/**
|
|
7178
|
+
* @param ctx {@link RendererContext} instance
|
|
7179
|
+
* @param pickingSystem {@link PickingSystem} instance
|
|
7180
|
+
*/
|
|
7181
|
+
constructor(ctx, pickingSystem) {
|
|
7182
|
+
this.ctx = ctx;
|
|
7183
|
+
this.pickingSystem = pickingSystem;
|
|
7184
|
+
this.camera.matrixAutoUpdate = false;
|
|
7185
|
+
this.camera.matrixWorldAutoUpdate = false;
|
|
7186
|
+
}
|
|
7187
|
+
/** Current zoom factor estimation */
|
|
7188
|
+
get zoomFactor() {
|
|
7189
|
+
const estimatedZoomFactor = this.estimateZoomFactor();
|
|
7190
|
+
const newZoomFactor = estimatedZoomFactor ?? this.prevZoomFactor ?? 1;
|
|
7191
|
+
if (estimatedZoomFactor) this.prevZoomFactor = estimatedZoomFactor;
|
|
7192
|
+
return newZoomFactor;
|
|
7193
|
+
}
|
|
7194
|
+
/**
|
|
7195
|
+
* Decompose the combined world-to-clip-space matrix into a rigid view matrix and a projection
|
|
7196
|
+
* matrix, then publish all four matrices (and their inverses) on the underlying camera.
|
|
7197
|
+
* @param mainMatrix Combined matrix mapping world space directly to clip space.
|
|
7198
|
+
*/
|
|
7199
|
+
setCameraProjection(mainMatrix) {
|
|
7200
|
+
if (!assertValidMatrix(mainMatrix, "setCameraProjection", externalLogger)) return;
|
|
7201
|
+
const M = this.mainMatrix4.fromArray(mainMatrix);
|
|
7202
|
+
const e = M.elements;
|
|
7203
|
+
this.mainMatrixInverse.copy(M).invert();
|
|
7204
|
+
this.linearSystem.set(e[0], e[4], e[8], e[1], e[5], e[9], e[3], e[7], e[11]);
|
|
7205
|
+
this.rhs.set(-e[12], -e[13], -e[15]);
|
|
7206
|
+
this.cameraPos.copy(this.rhs).applyMatrix3(this.linearSystem.invert());
|
|
7207
|
+
this.nearPoint.set(0, 0, 1).applyMatrix4(this.mainMatrixInverse);
|
|
7208
|
+
this.farPoint.set(0, 0, -1).applyMatrix4(this.mainMatrixInverse);
|
|
7209
|
+
this.viewDir.copy(this.farPoint).sub(this.nearPoint).normalize();
|
|
7210
|
+
this.up.set(0, 0, 1);
|
|
7211
|
+
if (Math.abs(this.up.dot(this.viewDir)) > .999) this.up.set(0, 1, 0);
|
|
7212
|
+
this.target.copy(this.cameraPos).add(this.viewDir);
|
|
7213
|
+
const camera = this.camera;
|
|
7214
|
+
camera.matrixWorld.lookAt(this.cameraPos, this.target, this.up);
|
|
7215
|
+
camera.matrixWorld.setPosition(this.cameraPos);
|
|
7216
|
+
camera.matrixWorldInverse.copy(camera.matrixWorld).invert();
|
|
7217
|
+
camera.projectionMatrix.multiplyMatrices(M, camera.matrixWorld);
|
|
7218
|
+
camera.projectionMatrixInverse.copy(camera.projectionMatrix).invert();
|
|
7219
|
+
}
|
|
7220
|
+
estimateZoomFactor() {
|
|
7221
|
+
const [bufferW, bufferH] = this.ctx.getDrawingBufferSizePx();
|
|
7222
|
+
if (bufferW <= 0 || bufferH <= 0) return;
|
|
7223
|
+
const worldPoint1 = this.pickingSystem.intersectPlane(this.ndcLeft, this.camera, this.intersectionPointLeft);
|
|
7224
|
+
const worldPoint2 = this.pickingSystem.intersectPlane(this.ndcRight, this.camera, this.intersectionPointRight);
|
|
7225
|
+
if (!worldPoint1 || !worldPoint2) return;
|
|
7226
|
+
const worldLength = worldPoint2.sub(worldPoint1).length();
|
|
7227
|
+
if (worldLength === 0) return;
|
|
7228
|
+
return bufferW / worldLength;
|
|
7229
|
+
}
|
|
7230
|
+
};
|
|
7231
|
+
//#endregion
|
|
6755
7232
|
//#region src/space/picking.ts
|
|
6756
7233
|
/**
|
|
6757
7234
|
* Picking subsystem.
|
|
@@ -6818,7 +7295,7 @@ var SceneSystem = class {
|
|
|
6818
7295
|
}
|
|
6819
7296
|
/** Iterator over all registered scene states */
|
|
6820
7297
|
get sceneStates() {
|
|
6821
|
-
return this.mapIdToSceneState.values()
|
|
7298
|
+
return Array.from(this.mapIdToSceneState.values());
|
|
6822
7299
|
}
|
|
6823
7300
|
/**
|
|
6824
7301
|
* Get the scale factor for a given scene.
|
|
@@ -6826,7 +7303,8 @@ var SceneSystem = class {
|
|
|
6826
7303
|
* @returns Scale factor (model space to world space)
|
|
6827
7304
|
*/
|
|
6828
7305
|
scaleFactor(sceneId) {
|
|
6829
|
-
|
|
7306
|
+
const sceneState = this.getSceneStateById(sceneId);
|
|
7307
|
+
return Math.abs(sceneState.worldMatrix.elements[0]);
|
|
6830
7308
|
}
|
|
6831
7309
|
/**
|
|
6832
7310
|
* Register a new scene from it's definition
|
|
@@ -7194,7 +7672,7 @@ var Renderer = class {
|
|
|
7194
7672
|
this.renderer.autoClear = !this.isExternalMode;
|
|
7195
7673
|
this.ctx = this.createRendererContext();
|
|
7196
7674
|
this.eventSystem = new EventSystem();
|
|
7197
|
-
this.lightsSystem = new LightsSystem();
|
|
7675
|
+
this.lightsSystem = new LightsSystem(this.ctx);
|
|
7198
7676
|
this.layerSystem = new LayerSystem(this.ctx);
|
|
7199
7677
|
this.viewportSystem = new ViewportSystem(this.ctx, this.eventSystem);
|
|
7200
7678
|
this.updatesSystem = new UpdatesSystem(this.ctx, this.layerSystem);
|
|
@@ -7293,6 +7771,18 @@ var Renderer = class {
|
|
|
7293
7771
|
return this.disposed;
|
|
7294
7772
|
}
|
|
7295
7773
|
/**
|
|
7774
|
+
* Loads a GLB file and returns a Three.js model.
|
|
7775
|
+
* @param models - The models to load.
|
|
7776
|
+
*/
|
|
7777
|
+
async loadModel(models) {
|
|
7778
|
+
this.init();
|
|
7779
|
+
const { root, sceneDef } = await loadGLB(models);
|
|
7780
|
+
const scene = this.viewportSystem.initScene(sceneDef);
|
|
7781
|
+
scene.background = new Color(15461355);
|
|
7782
|
+
scene.add(root);
|
|
7783
|
+
this.lightsSystem.initLights(scene);
|
|
7784
|
+
}
|
|
7785
|
+
/**
|
|
7296
7786
|
* Initializes viewport and scene with the given scene definition.
|
|
7297
7787
|
* Should be called once on startup. Repeated calls will produce console warnings.
|
|
7298
7788
|
* @param sceneDef {@link SceneDef} to render
|
|
@@ -7361,6 +7851,7 @@ var Renderer = class {
|
|
|
7361
7851
|
} else if (sceneState.scene.children.length === 0) {
|
|
7362
7852
|
const root = this.layerSystem.buildScene(sceneState.sceneDef);
|
|
7363
7853
|
scene.add(root);
|
|
7854
|
+
this.lightsSystem.initLights(scene);
|
|
7364
7855
|
}
|
|
7365
7856
|
}
|
|
7366
7857
|
const justLoaded = loadedChanged && sceneState.loaded;
|
|
@@ -7368,8 +7859,7 @@ var Renderer = class {
|
|
|
7368
7859
|
this.viewportSystem.updatePtScale(id);
|
|
7369
7860
|
const hasDefsUpdated = this.updatesSystem.processPendingUpdates(frustum, 3);
|
|
7370
7861
|
const forceRedraw = hasControlsUpdated || hasDefsUpdated || justLoaded || this.isExternalMode || this.ui;
|
|
7371
|
-
|
|
7372
|
-
this.lightsSystem.updateLights(scene, camera);
|
|
7862
|
+
this.lightsSystem.updateLights(sceneState, camera);
|
|
7373
7863
|
if (this.needsRedraw || forceRedraw) this.renderer.render(scene, camera);
|
|
7374
7864
|
if (hasDefsUpdated || this.needsRedraw) this.viewportSystem.invalidateSceneBounds(sceneState);
|
|
7375
7865
|
}
|
|
@@ -7475,4 +7965,4 @@ var Renderer = class {
|
|
|
7475
7965
|
}
|
|
7476
7966
|
};
|
|
7477
7967
|
//#endregion
|
|
7478
|
-
export { Polygon, Rect, Renderer, isImageDef, isImageLayer, isLayerDef, isLayerLayer, isLineDef, isLineLayer, isShapeDef, isShapeLayer, isTextDef, isTextLayer };
|
|
7968
|
+
export { Polygon, Rect, Renderer, glb2gltf, isImageDef, isImageLayer, isLayerDef, isLayerLayer, isLineDef, isLineLayer, isModelDef, isModelLayer, isShapeDef, isShapeLayer, isTextDef, isTextLayer, splitGltf };
|