fl-web-component 2.1.1-beta.1 → 2.1.1-beta.11
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/fl-web-component.common.js +1599 -890
- package/dist/fl-web-component.common.js.map +1 -1
- package/dist/fl-web-component.css +1 -1
- package/package.json +1 -1
- package/packages/components/com-flcanvas/components/entityFormatting.js +19 -14
- package/packages/components/com-flcanvas/index.vue +350 -302
- package/packages/components/com-graphics/index.vue +941 -159
- package/src/utils/instance-parser.js +5 -0
|
@@ -165,9 +165,15 @@ import { onContextHandle } from './component/context';
|
|
|
165
165
|
|
|
166
166
|
const isDebug = process.env.NODE_ENV !== 'production' || process.env.VUE_APP_IS_WATCH === true;
|
|
167
167
|
// const isDebug = false;
|
|
168
|
-
const
|
|
168
|
+
const COLLISION_CONTACT_EPSILON = 1;
|
|
169
169
|
const COLLISION_OBB_AXIS_EPSILON = 1e-8;
|
|
170
170
|
const COLLISION_DEBUG_GROUP_NAME = '__collision_obb_debug_group__';
|
|
171
|
+
const OCCLUSION_DEBUG_GROUP_NAME = '__occlusion_bounds_debug_group__';
|
|
172
|
+
const OCCLUSION_DEBUG_FLAG_COLORS = {
|
|
173
|
+
1: 0xffc107,
|
|
174
|
+
2: 0x409eff,
|
|
175
|
+
3: 0xff4dff,
|
|
176
|
+
};
|
|
171
177
|
|
|
172
178
|
export default {
|
|
173
179
|
name: 'FlModel',
|
|
@@ -198,8 +204,8 @@ export default {
|
|
|
198
204
|
occlusionState: {
|
|
199
205
|
// enabled: false,
|
|
200
206
|
enabled: true,
|
|
201
|
-
|
|
202
|
-
previewEnabled: isDebug,
|
|
207
|
+
previewEnabled: false,
|
|
208
|
+
// previewEnabled: isDebug,
|
|
203
209
|
bufferWidth: 1700,
|
|
204
210
|
sampleStride: 1,
|
|
205
211
|
minSampleCount: 5,
|
|
@@ -215,7 +221,11 @@ export default {
|
|
|
215
221
|
isPaused: false,
|
|
216
222
|
},
|
|
217
223
|
isolateMode: false,
|
|
218
|
-
|
|
224
|
+
sceneBoundingBoxDebugEnabled: false,
|
|
225
|
+
// collisionObbDebugEnabled: isDebug,
|
|
226
|
+
collisionObbDebugEnabled: false,
|
|
227
|
+
// occlusionBoundsDebugEnabled: isDebug,
|
|
228
|
+
occlusionBoundsDebugEnabled: false,
|
|
219
229
|
};
|
|
220
230
|
},
|
|
221
231
|
watch: {
|
|
@@ -224,6 +234,13 @@ export default {
|
|
|
224
234
|
this.detachTransformControls();
|
|
225
235
|
}
|
|
226
236
|
},
|
|
237
|
+
sceneBoundingBoxDebugEnabled(val) {
|
|
238
|
+
if (val) {
|
|
239
|
+
this.refreshSceneBoundingBoxHelper(true);
|
|
240
|
+
} else {
|
|
241
|
+
this.hideSceneBoundingBox();
|
|
242
|
+
}
|
|
243
|
+
},
|
|
227
244
|
},
|
|
228
245
|
beforeCreate() {
|
|
229
246
|
this.spaceUp = true;
|
|
@@ -258,6 +275,7 @@ export default {
|
|
|
258
275
|
'sceneBoundingBox',
|
|
259
276
|
'sceneBoundingBoxHelper',
|
|
260
277
|
'collisionObbDebugGroup',
|
|
278
|
+
'occlusionBoundsDebugGroup',
|
|
261
279
|
];
|
|
262
280
|
arr.forEach(item => {
|
|
263
281
|
this[item] = null;
|
|
@@ -279,6 +297,7 @@ export default {
|
|
|
279
297
|
'isDragging',
|
|
280
298
|
'boundingBoxVisible',
|
|
281
299
|
'collisionObbDebugEnabled',
|
|
300
|
+
'occlusionBoundsDebugEnabled',
|
|
282
301
|
].forEach(item => {
|
|
283
302
|
this[item] = false;
|
|
284
303
|
});
|
|
@@ -360,11 +379,16 @@ export default {
|
|
|
360
379
|
wheelTimeout: null,
|
|
361
380
|
},
|
|
362
381
|
},
|
|
382
|
+
cameraControlMoveObserver: {
|
|
383
|
+
enabled: false,
|
|
384
|
+
interval: 500,
|
|
385
|
+
},
|
|
363
386
|
streamLoader: null,
|
|
364
387
|
// isPerformingInitialCentering: false, // 标记是否正在执行初始居中
|
|
365
388
|
isObserverEnabled: true, // 标记相机变更观察者是否启用
|
|
366
389
|
sceneBoxes: new Map(),
|
|
367
390
|
documentModelIds: new Map(),
|
|
391
|
+
documentSceneBoundsIds: new Map(),
|
|
368
392
|
outlineInstanceProxyMap: new Map(),
|
|
369
393
|
occlusionWorker: null,
|
|
370
394
|
occlusionWorkerRequestMap: new Map(),
|
|
@@ -1204,6 +1228,26 @@ export default {
|
|
|
1204
1228
|
if (instancedMesh && instancedMesh.userData) return instancedMesh.userData.copyMatrix || null;
|
|
1205
1229
|
return null;
|
|
1206
1230
|
},
|
|
1231
|
+
setInstanceCullingVisible(instancedMesh, instanceId, visible) {
|
|
1232
|
+
const instanceState = this.getInstanceState(instancedMesh, instanceId);
|
|
1233
|
+
if (!instanceState || typeof instanceState.instanceIndex !== 'number') return;
|
|
1234
|
+
|
|
1235
|
+
const copyMatrix =
|
|
1236
|
+
instanceState.copyMatrix || this.getInstanceCopyMatrix(instancedMesh, instanceId);
|
|
1237
|
+
if (!copyMatrix) return;
|
|
1238
|
+
|
|
1239
|
+
const shouldShow =
|
|
1240
|
+
visible && instanceState.visible !== false && this.isSourceVisible(instanceId, instancedMesh);
|
|
1241
|
+
const matrix = new this.THREE.Matrix4().copy(copyMatrix);
|
|
1242
|
+
if (!shouldShow) {
|
|
1243
|
+
matrix.makeTranslation(9999999, 9999999, 9999999);
|
|
1244
|
+
this.removeOutlineObject(instancedMesh, instanceState.instanceIndex);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
instancedMesh.setMatrixAt(instanceState.instanceIndex, matrix);
|
|
1248
|
+
instancedMesh.instanceMatrix.needsUpdate = true;
|
|
1249
|
+
instanceState.cullingVisible = !!visible;
|
|
1250
|
+
},
|
|
1207
1251
|
isSourceHiddenInstance(instancedMesh, instanceId) {
|
|
1208
1252
|
const instanceState = this.getInstanceState(instancedMesh, instanceId);
|
|
1209
1253
|
if (instanceState && instanceState.sourceVisible !== undefined) {
|
|
@@ -1469,71 +1513,178 @@ export default {
|
|
|
1469
1513
|
updateGlobalSceneBoundingBox() {
|
|
1470
1514
|
const state = this.noObserver || {};
|
|
1471
1515
|
const occScene = state.occlusionState && state.occlusionState._occScene;
|
|
1516
|
+
const mergedBox = new this.THREE.Box3();
|
|
1517
|
+
let hasValidBox = false;
|
|
1518
|
+
const mergeBox = box => {
|
|
1519
|
+
if (!box || !box.isBox3 || box.isEmpty()) return;
|
|
1520
|
+
if (!hasValidBox) {
|
|
1521
|
+
mergedBox.copy(box);
|
|
1522
|
+
hasValidBox = true;
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
mergedBox.union(box);
|
|
1526
|
+
};
|
|
1472
1527
|
|
|
1473
|
-
//
|
|
1528
|
+
// 离屏遮挡场景只包含参与遮挡的模型,需要继续合并场景范围索引中的二维图形范围。
|
|
1474
1529
|
if (occScene && occScene.children && occScene.children.length > 0) {
|
|
1475
1530
|
const occBox = new this.THREE.Box3().setFromObject(occScene);
|
|
1476
|
-
|
|
1477
|
-
this.sceneBoundingBox = occBox;
|
|
1478
|
-
return this.sceneBoundingBox;
|
|
1479
|
-
}
|
|
1531
|
+
mergeBox(occBox);
|
|
1480
1532
|
}
|
|
1481
1533
|
|
|
1482
|
-
//
|
|
1483
|
-
if (this.
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
minZ = Infinity;
|
|
1487
|
-
let maxX = -Infinity,
|
|
1488
|
-
maxY = -Infinity,
|
|
1489
|
-
maxZ = -Infinity;
|
|
1490
|
-
this._boxIndex.forEach(box => {
|
|
1491
|
-
if (!box || !box.isBox3 || box.isEmpty()) return;
|
|
1492
|
-
const mn = box.min;
|
|
1493
|
-
const mx = box.max;
|
|
1494
|
-
if (mn.x < minX) minX = mn.x;
|
|
1495
|
-
if (mn.y < minY) minY = mn.y;
|
|
1496
|
-
if (mn.z < minZ) minZ = mn.z;
|
|
1497
|
-
if (mx.x > maxX) maxX = mx.x;
|
|
1498
|
-
if (mx.y > maxY) maxY = mx.y;
|
|
1499
|
-
if (mx.z > maxZ) maxZ = mx.z;
|
|
1534
|
+
// sceneBoundsIndex 包含二维和三维图形,是全场景 dolly 距离的主要范围来源。
|
|
1535
|
+
if (this._sceneBoundsIndex && this._sceneBoundsIndex.size > 0) {
|
|
1536
|
+
this._sceneBoundsIndex.forEach(box => {
|
|
1537
|
+
mergeBox(box);
|
|
1500
1538
|
});
|
|
1501
|
-
if (
|
|
1502
|
-
Number.isFinite(minX) &&
|
|
1503
|
-
Number.isFinite(minY) &&
|
|
1504
|
-
Number.isFinite(minZ) &&
|
|
1505
|
-
Number.isFinite(maxX) &&
|
|
1506
|
-
Number.isFinite(maxY) &&
|
|
1507
|
-
Number.isFinite(maxZ)
|
|
1508
|
-
) {
|
|
1509
|
-
this.sceneBoundingBox = new this.THREE.Box3(
|
|
1510
|
-
new this.THREE.Vector3(minX, minY, minZ),
|
|
1511
|
-
new this.THREE.Vector3(maxX, maxY, maxZ)
|
|
1512
|
-
);
|
|
1513
|
-
return this.sceneBoundingBox;
|
|
1514
|
-
}
|
|
1515
1539
|
}
|
|
1516
1540
|
|
|
1517
1541
|
const boxes = Array.from((state.sceneBoxes && state.sceneBoxes.values()) || []);
|
|
1518
1542
|
if (boxes.length > 0) {
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
firstBox.union(boxes[i]);
|
|
1543
|
+
for (let i = 0; i < boxes.length; i++) {
|
|
1544
|
+
mergeBox(boxes[i]);
|
|
1522
1545
|
}
|
|
1523
|
-
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
if (hasValidBox) {
|
|
1549
|
+
this.sceneBoundingBox = mergedBox;
|
|
1550
|
+
this.refreshSceneBoundingBoxHelper(this.sceneBoundingBoxDebugEnabled);
|
|
1524
1551
|
return this.sceneBoundingBox;
|
|
1525
1552
|
}
|
|
1526
1553
|
|
|
1527
1554
|
this.sceneBoundingBox = new this.THREE.Box3();
|
|
1555
|
+
this.refreshSceneBoundingBoxHelper(this.sceneBoundingBoxDebugEnabled);
|
|
1528
1556
|
return this.sceneBoundingBox;
|
|
1529
1557
|
},
|
|
1558
|
+
upsertIndexedBox(indexMap, modelId, box) {
|
|
1559
|
+
if (!indexMap || !modelId || !box || !box.isBox3) return null;
|
|
1560
|
+
const key = String(modelId);
|
|
1561
|
+
const existingBox = indexMap.get(key);
|
|
1562
|
+
if (!existingBox) {
|
|
1563
|
+
indexMap.set(key, box);
|
|
1564
|
+
return box;
|
|
1565
|
+
}
|
|
1566
|
+
existingBox.union(box);
|
|
1567
|
+
return existingBox;
|
|
1568
|
+
},
|
|
1569
|
+
createOcclusionProxyBox(box) {
|
|
1570
|
+
if (!box || !box.isBox3) return null;
|
|
1571
|
+
const proxyBox = box.clone();
|
|
1572
|
+
proxyBox.userData = { ...(box.userData || {}) };
|
|
1573
|
+
delete proxyBox.userData.occlusionProxyBoxes;
|
|
1574
|
+
return proxyBox;
|
|
1575
|
+
},
|
|
1576
|
+
mergeModelBoxUserData(targetBox, sourceBox) {
|
|
1577
|
+
if (!targetBox || !sourceBox || !sourceBox.userData) return;
|
|
1578
|
+
const sourceUserData = sourceBox.userData;
|
|
1579
|
+
if (!targetBox.userData) {
|
|
1580
|
+
targetBox.userData = sourceUserData;
|
|
1581
|
+
return;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
const targetUserData = targetBox.userData;
|
|
1585
|
+
const targetProxyBoxes = Array.isArray(targetUserData.occlusionProxyBoxes)
|
|
1586
|
+
? targetUserData.occlusionProxyBoxes
|
|
1587
|
+
: [this.createOcclusionProxyBox(targetBox)].filter(Boolean);
|
|
1588
|
+
const sourceProxyBoxes = Array.isArray(sourceUserData.occlusionProxyBoxes)
|
|
1589
|
+
? sourceUserData.occlusionProxyBoxes
|
|
1590
|
+
: [this.createOcclusionProxyBox(sourceBox)].filter(Boolean);
|
|
1591
|
+
targetUserData.visible = targetUserData.visible !== false || sourceUserData.visible !== false;
|
|
1592
|
+
targetUserData.sourceVisible =
|
|
1593
|
+
targetUserData.sourceVisible !== false || sourceUserData.sourceVisible !== false;
|
|
1594
|
+
targetUserData.transparent = !!targetUserData.transparent && !!sourceUserData.transparent;
|
|
1595
|
+
targetUserData.mergedBoxCount = (targetUserData.mergedBoxCount || 1) + 1;
|
|
1596
|
+
// 多图元模型分别参与遮挡测试,任一图元可见时都会保留整个模型。
|
|
1597
|
+
targetUserData.occlusionEnabled =
|
|
1598
|
+
!!targetUserData.occlusionEnabled && !!sourceUserData.occlusionEnabled;
|
|
1599
|
+
targetUserData.occlusionProxyBoxes = targetProxyBoxes.concat(sourceProxyBoxes);
|
|
1600
|
+
|
|
1601
|
+
const targetObbs = Array.isArray(targetUserData.collisionObbs)
|
|
1602
|
+
? targetUserData.collisionObbs
|
|
1603
|
+
: [];
|
|
1604
|
+
const sourceObbs = Array.isArray(sourceUserData.collisionObbs)
|
|
1605
|
+
? sourceUserData.collisionObbs
|
|
1606
|
+
: [];
|
|
1607
|
+
if (sourceObbs.length > 0) {
|
|
1608
|
+
targetUserData.collisionObbs = targetObbs.concat(sourceObbs);
|
|
1609
|
+
}
|
|
1610
|
+
},
|
|
1611
|
+
_createCollisionObb(center, halfSize, rotation, matrix) {
|
|
1612
|
+
const worldCenter = center.clone();
|
|
1613
|
+
const worldHalfSize = halfSize.clone();
|
|
1614
|
+
const worldRotation = new this.THREE.Matrix3();
|
|
1615
|
+
|
|
1616
|
+
if (!matrix) {
|
|
1617
|
+
return new OBB(worldCenter, worldHalfSize, rotation.clone());
|
|
1618
|
+
}
|
|
1619
|
+
|
|
1620
|
+
worldCenter.applyMatrix4(matrix);
|
|
1621
|
+
|
|
1622
|
+
const localAxes = [
|
|
1623
|
+
new this.THREE.Vector3(),
|
|
1624
|
+
new this.THREE.Vector3(),
|
|
1625
|
+
new this.THREE.Vector3(),
|
|
1626
|
+
];
|
|
1627
|
+
rotation.extractBasis(localAxes[0], localAxes[1], localAxes[2]);
|
|
1628
|
+
|
|
1629
|
+
const linearMatrix = new this.THREE.Matrix3().setFromMatrix4(matrix);
|
|
1630
|
+
const worldAxes = localAxes.map(axis => axis.clone().applyMatrix3(linearMatrix));
|
|
1631
|
+
const halfSizes = [halfSize.x, halfSize.y, halfSize.z];
|
|
1632
|
+
|
|
1633
|
+
for (let i = 0; i < 3; i++) {
|
|
1634
|
+
const axisLength = worldAxes[i].length();
|
|
1635
|
+
if (axisLength <= COLLISION_OBB_AXIS_EPSILON) {
|
|
1636
|
+
worldAxes[i].copy(localAxes[i]);
|
|
1637
|
+
continue;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
worldAxes[i].multiplyScalar(1 / axisLength);
|
|
1641
|
+
halfSizes[i] *= axisLength;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
worldHalfSize.set(halfSizes[0], halfSizes[1], halfSizes[2]);
|
|
1645
|
+
worldRotation.set(
|
|
1646
|
+
worldAxes[0].x,
|
|
1647
|
+
worldAxes[1].x,
|
|
1648
|
+
worldAxes[2].x,
|
|
1649
|
+
worldAxes[0].y,
|
|
1650
|
+
worldAxes[1].y,
|
|
1651
|
+
worldAxes[2].y,
|
|
1652
|
+
worldAxes[0].z,
|
|
1653
|
+
worldAxes[1].z,
|
|
1654
|
+
worldAxes[2].z
|
|
1655
|
+
);
|
|
1656
|
+
|
|
1657
|
+
return new OBB(worldCenter, worldHalfSize, worldRotation);
|
|
1658
|
+
},
|
|
1659
|
+
_createCollisionObbFromBox(box, matrix) {
|
|
1660
|
+
const center = box.getCenter(new this.THREE.Vector3());
|
|
1661
|
+
const halfSize = box.getSize(new this.THREE.Vector3()).multiplyScalar(0.5);
|
|
1662
|
+
const rotation = new this.THREE.Matrix3().identity();
|
|
1663
|
+
return this._createCollisionObb(center, halfSize, rotation, matrix);
|
|
1664
|
+
},
|
|
1665
|
+
upsertModelBox(modelId, box) {
|
|
1666
|
+
if (!this._boxIndex || !modelId || !box || !box.isBox3) return;
|
|
1667
|
+
const key = String(modelId);
|
|
1668
|
+
const existingBox = this._boxIndex.get(key);
|
|
1669
|
+
if (!existingBox) {
|
|
1670
|
+
this._boxIndex.set(key, box);
|
|
1671
|
+
return;
|
|
1672
|
+
}
|
|
1673
|
+
this.mergeModelBoxUserData(existingBox, box);
|
|
1674
|
+
existingBox.union(box);
|
|
1675
|
+
},
|
|
1530
1676
|
setBoxIndex(boxJson, documentId, isAdd = true) {
|
|
1531
1677
|
if (!this._boxIndex) this._boxIndex = new Map();
|
|
1678
|
+
if (!this._sceneBoundsIndex) this._sceneBoundsIndex = new Map();
|
|
1532
1679
|
if (!documentId) {
|
|
1533
1680
|
this._boxIndex.clear();
|
|
1681
|
+
this._sceneBoundsIndex.clear();
|
|
1534
1682
|
if (this.noObserver && this.noObserver.documentModelIds) {
|
|
1535
1683
|
this.noObserver.documentModelIds.clear();
|
|
1536
1684
|
}
|
|
1685
|
+
if (this.noObserver && this.noObserver.documentSceneBoundsIds) {
|
|
1686
|
+
this.noObserver.documentSceneBoundsIds.clear();
|
|
1687
|
+
}
|
|
1537
1688
|
this.hasExecutedCentering = false;
|
|
1538
1689
|
this.buildOctreeFromBoxIndex();
|
|
1539
1690
|
return;
|
|
@@ -1542,19 +1693,35 @@ export default {
|
|
|
1542
1693
|
if (isAdd && boxJson) {
|
|
1543
1694
|
const arr = boxJson ? boxJson : [];
|
|
1544
1695
|
const modelIds = new Set();
|
|
1696
|
+
const sceneBoundIds = new Set();
|
|
1545
1697
|
const validGeomTypes = new Set(Object.values(GEOM_TYPES));
|
|
1546
1698
|
for (let i = 0; i < arr.length; i++) {
|
|
1547
1699
|
const it = arr[i];
|
|
1548
1700
|
if (!validGeomTypes.has(it.geomType)) {
|
|
1549
1701
|
continue;
|
|
1550
1702
|
}
|
|
1551
|
-
|
|
1703
|
+
const minSource = it.min;
|
|
1704
|
+
const maxSource = it.max;
|
|
1705
|
+
const hasValidMinMax =
|
|
1706
|
+
minSource &&
|
|
1707
|
+
maxSource &&
|
|
1708
|
+
minSource.length >= 3 &&
|
|
1709
|
+
maxSource.length >= 3 &&
|
|
1710
|
+
Number.isFinite(minSource[0]) &&
|
|
1711
|
+
Number.isFinite(minSource[1]) &&
|
|
1712
|
+
Number.isFinite(minSource[2]) &&
|
|
1713
|
+
Number.isFinite(maxSource[0]) &&
|
|
1714
|
+
Number.isFinite(maxSource[1]) &&
|
|
1715
|
+
Number.isFinite(maxSource[2]);
|
|
1716
|
+
if (!hasValidMinMax) {
|
|
1552
1717
|
continue;
|
|
1553
1718
|
}
|
|
1719
|
+
const obbData = it.obb || [];
|
|
1720
|
+
const modelId = String(it.id);
|
|
1554
1721
|
|
|
1555
1722
|
// 使用新的 min/max 数据结构
|
|
1556
|
-
const min = new this.THREE.Vector3(
|
|
1557
|
-
const max = new this.THREE.Vector3(
|
|
1723
|
+
const min = new this.THREE.Vector3(minSource[0], minSource[1], minSource[2]);
|
|
1724
|
+
const max = new this.THREE.Vector3(maxSource[0], maxSource[1], maxSource[2]);
|
|
1558
1725
|
|
|
1559
1726
|
const sourceBox = new this.THREE.Box3(min.clone(), max.clone());
|
|
1560
1727
|
// 八叉树仍使用 AABB 粗筛,最终碰撞使用 sourceBox 派生的 OBB 精筛
|
|
@@ -1588,10 +1755,18 @@ export default {
|
|
|
1588
1755
|
boxThree.applyMatrix4(collisionMatrix);
|
|
1589
1756
|
}
|
|
1590
1757
|
|
|
1758
|
+
this.upsertIndexedBox(this._sceneBoundsIndex, modelId, boxThree.clone());
|
|
1759
|
+
sceneBoundIds.add(modelId);
|
|
1760
|
+
|
|
1761
|
+
// 没有 OBB 数据的二维图形只参与全场景范围,不纳入碰撞和遮挡索引。
|
|
1762
|
+
if (obbData.length === 0) {
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1591
1766
|
const userData = {
|
|
1592
1767
|
flag: it.flag || 1, // 默认为 1
|
|
1593
1768
|
geomType: it.geomType,
|
|
1594
|
-
obbData
|
|
1769
|
+
obbData, // 存储原始 OBB 数据
|
|
1595
1770
|
transparent: it.transp > 0,
|
|
1596
1771
|
sourceVisible: it.visible === false ? false : true,
|
|
1597
1772
|
visible: it.visible === false ? false : true, // 默认显示
|
|
@@ -1605,8 +1780,7 @@ export default {
|
|
|
1605
1780
|
// userData.matrix = new this.THREE.Matrix4().identity();
|
|
1606
1781
|
}
|
|
1607
1782
|
// 如果是 flag=3,解析并存储中心点、半轴长、旋转矩阵
|
|
1608
|
-
else if (it.flag === 3 &&
|
|
1609
|
-
const obbData = it.obb;
|
|
1783
|
+
else if (it.flag === 3 && obbData.length === 15) {
|
|
1610
1784
|
const center = new this.THREE.Vector3(obbData[0], obbData[1], obbData[2]);
|
|
1611
1785
|
const halfSize = new this.THREE.Vector3(obbData[3], obbData[4], obbData[5]);
|
|
1612
1786
|
const rotation = new this.THREE.Matrix3().fromArray(obbData.slice(6, 15));
|
|
@@ -1629,27 +1803,26 @@ export default {
|
|
|
1629
1803
|
halfSize: halfSize,
|
|
1630
1804
|
};
|
|
1631
1805
|
|
|
1632
|
-
const collisionObb =
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1806
|
+
const collisionObb = this._createCollisionObb(
|
|
1807
|
+
center,
|
|
1808
|
+
halfSize,
|
|
1809
|
+
rotation,
|
|
1810
|
+
collisionMatrix
|
|
1811
|
+
);
|
|
1636
1812
|
userData.collisionObbs = [collisionObb];
|
|
1637
1813
|
}
|
|
1638
1814
|
// 如果是 flag=2,解析并存储多个子包围盒 (min, max)
|
|
1639
|
-
else if (it.flag === 2 &&
|
|
1815
|
+
else if (it.flag === 2 && obbData.length > 0) {
|
|
1640
1816
|
const subBoxes = [];
|
|
1641
1817
|
const collisionObbs = [];
|
|
1642
|
-
for (let k = 0; k <
|
|
1643
|
-
if (k + 5 <
|
|
1644
|
-
const min = new this.THREE.Vector3(
|
|
1645
|
-
const max = new this.THREE.Vector3(
|
|
1818
|
+
for (let k = 0; k < obbData.length; k += 6) {
|
|
1819
|
+
if (k + 5 < obbData.length) {
|
|
1820
|
+
const min = new this.THREE.Vector3(obbData[k], obbData[k + 1], obbData[k + 2]);
|
|
1821
|
+
const max = new this.THREE.Vector3(obbData[k + 3], obbData[k + 4], obbData[k + 5]);
|
|
1646
1822
|
subBoxes.push({ min, max });
|
|
1647
1823
|
|
|
1648
1824
|
const subBox = new this.THREE.Box3(min.clone(), max.clone());
|
|
1649
|
-
const collisionObb =
|
|
1650
|
-
if (collisionMatrix) {
|
|
1651
|
-
collisionObb.applyMatrix4(collisionMatrix);
|
|
1652
|
-
}
|
|
1825
|
+
const collisionObb = this._createCollisionObbFromBox(subBox, collisionMatrix);
|
|
1653
1826
|
collisionObbs.push(collisionObb);
|
|
1654
1827
|
}
|
|
1655
1828
|
}
|
|
@@ -1660,20 +1833,17 @@ export default {
|
|
|
1660
1833
|
}
|
|
1661
1834
|
|
|
1662
1835
|
if (!userData.collisionObbs || userData.collisionObbs.length === 0) {
|
|
1663
|
-
const collisionObb =
|
|
1664
|
-
if (collisionMatrix) {
|
|
1665
|
-
collisionObb.applyMatrix4(collisionMatrix);
|
|
1666
|
-
}
|
|
1836
|
+
const collisionObb = this._createCollisionObbFromBox(sourceBox, collisionMatrix);
|
|
1667
1837
|
userData.collisionObbs = [collisionObb];
|
|
1668
1838
|
}
|
|
1669
1839
|
|
|
1670
1840
|
boxThree.userData = userData;
|
|
1671
1841
|
|
|
1672
|
-
|
|
1673
|
-
this._boxIndex.set(modelId, boxThree);
|
|
1842
|
+
this.upsertModelBox(modelId, boxThree);
|
|
1674
1843
|
modelIds.add(modelId);
|
|
1675
1844
|
}
|
|
1676
1845
|
this.noObserver.documentModelIds.set(documentId, modelIds);
|
|
1846
|
+
this.noObserver.documentSceneBoundsIds.set(documentId, sceneBoundIds);
|
|
1677
1847
|
} else {
|
|
1678
1848
|
const modelIds = this.noObserver.documentModelIds.get(documentId);
|
|
1679
1849
|
if (modelIds) {
|
|
@@ -1687,14 +1857,38 @@ export default {
|
|
|
1687
1857
|
this.hasExecutedCentering = false;
|
|
1688
1858
|
}
|
|
1689
1859
|
}
|
|
1860
|
+
const sceneBoundIds =
|
|
1861
|
+
this.noObserver.documentSceneBoundsIds &&
|
|
1862
|
+
this.noObserver.documentSceneBoundsIds.get(documentId);
|
|
1863
|
+
if (sceneBoundIds) {
|
|
1864
|
+
sceneBoundIds.forEach(id => {
|
|
1865
|
+
this._sceneBoundsIndex.delete(id);
|
|
1866
|
+
});
|
|
1867
|
+
this.noObserver.documentSceneBoundsIds.delete(documentId);
|
|
1868
|
+
}
|
|
1690
1869
|
}
|
|
1691
1870
|
this.buildOctreeFromBoxIndex();
|
|
1692
1871
|
this.refreshCollisionObbDebug();
|
|
1872
|
+
this.refreshOcclusionBoundsDebug();
|
|
1693
1873
|
console.log('time end', Date.now());
|
|
1694
1874
|
},
|
|
1695
1875
|
shouldApplyOcclusionByBox(box) {
|
|
1696
1876
|
return !!(box && box.userData && box.userData.occlusionEnabled);
|
|
1697
1877
|
},
|
|
1878
|
+
appendOcclusionCandidates(candidates, modelId, box, frustum) {
|
|
1879
|
+
if (!box) return;
|
|
1880
|
+
const userData = box.userData || {};
|
|
1881
|
+
const proxyBoxes = Array.isArray(userData.occlusionProxyBoxes)
|
|
1882
|
+
? userData.occlusionProxyBoxes
|
|
1883
|
+
: [box];
|
|
1884
|
+
|
|
1885
|
+
for (let i = 0; i < proxyBoxes.length; i++) {
|
|
1886
|
+
const proxyBox = proxyBoxes[i];
|
|
1887
|
+
if (!proxyBox || (proxyBox.userData && proxyBox.userData.visible === false)) continue;
|
|
1888
|
+
if (frustum && !frustum.intersectsBox(proxyBox)) continue;
|
|
1889
|
+
candidates.push({ modelId, box: proxyBox });
|
|
1890
|
+
}
|
|
1891
|
+
},
|
|
1698
1892
|
tryInitialCenterAfterBoundsReady() {
|
|
1699
1893
|
if (this.hasExecutedCentering || this.userInteracting) return;
|
|
1700
1894
|
if (
|
|
@@ -1703,7 +1897,9 @@ export default {
|
|
|
1703
1897
|
this.sceneBoundingBox.isEmpty()
|
|
1704
1898
|
)
|
|
1705
1899
|
return;
|
|
1706
|
-
|
|
1900
|
+
const hasBoxIndex = this._boxIndex && this._boxIndex.size > 0;
|
|
1901
|
+
const hasSceneBoundsIndex = this._sceneBoundsIndex && this._sceneBoundsIndex.size > 0;
|
|
1902
|
+
if (!hasBoxIndex && !hasSceneBoundsIndex) return;
|
|
1707
1903
|
|
|
1708
1904
|
this.smartModelCenter(this.sceneBoundingBox, 0);
|
|
1709
1905
|
},
|
|
@@ -1805,6 +2001,53 @@ export default {
|
|
|
1805
2001
|
}
|
|
1806
2002
|
node.items.push({ id, box });
|
|
1807
2003
|
},
|
|
2004
|
+
_buildCollisionOctreeFromEntries(entries) {
|
|
2005
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
2006
|
+
return null;
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
let minX = Infinity;
|
|
2010
|
+
let minY = Infinity;
|
|
2011
|
+
let minZ = Infinity;
|
|
2012
|
+
let maxX = -Infinity;
|
|
2013
|
+
let maxY = -Infinity;
|
|
2014
|
+
let maxZ = -Infinity;
|
|
2015
|
+
|
|
2016
|
+
entries.forEach(item => {
|
|
2017
|
+
const box = item && item.box;
|
|
2018
|
+
if (!box || !box.isBox3) return;
|
|
2019
|
+
const mn = box.min;
|
|
2020
|
+
const mx = box.max;
|
|
2021
|
+
if (mn.x < minX) minX = mn.x;
|
|
2022
|
+
if (mn.y < minY) minY = mn.y;
|
|
2023
|
+
if (mn.z < minZ) minZ = mn.z;
|
|
2024
|
+
if (mx.x > maxX) maxX = mx.x;
|
|
2025
|
+
if (mx.y > maxY) maxY = mx.y;
|
|
2026
|
+
if (mx.z > maxZ) maxZ = mx.z;
|
|
2027
|
+
});
|
|
2028
|
+
|
|
2029
|
+
if (minX === Infinity) {
|
|
2030
|
+
return null;
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
const rootBox = new this.THREE.Box3(
|
|
2034
|
+
new this.THREE.Vector3(minX, minY, minZ),
|
|
2035
|
+
new this.THREE.Vector3(maxX, maxY, maxZ)
|
|
2036
|
+
);
|
|
2037
|
+
const root = {
|
|
2038
|
+
box: rootBox,
|
|
2039
|
+
items: [],
|
|
2040
|
+
children: null,
|
|
2041
|
+
depth: 0,
|
|
2042
|
+
};
|
|
2043
|
+
|
|
2044
|
+
entries.forEach(item => {
|
|
2045
|
+
if (!item || !item.box) return;
|
|
2046
|
+
this._octreeInsert(root, String(item.id), item.box);
|
|
2047
|
+
});
|
|
2048
|
+
|
|
2049
|
+
return root;
|
|
2050
|
+
},
|
|
1808
2051
|
_isCollisionBoxAvailable(box) {
|
|
1809
2052
|
if (!box || !box.isBox3 || box.isEmpty()) return false;
|
|
1810
2053
|
if (box.userData && box.userData.visible === false) return false;
|
|
@@ -1935,7 +2178,8 @@ export default {
|
|
|
1935
2178
|
_buildCollisionPairMap() {
|
|
1936
2179
|
const pairMap = new Map();
|
|
1937
2180
|
const collisionPairs = this.getOctreeCollisionPairs();
|
|
1938
|
-
const pairs =
|
|
2181
|
+
const pairs =
|
|
2182
|
+
collisionPairs && Array.isArray(collisionPairs.pairs) ? collisionPairs.pairs : [];
|
|
1939
2183
|
for (let i = 0; i < pairs.length; i++) {
|
|
1940
2184
|
const pair = pairs[i];
|
|
1941
2185
|
if (!pair) continue;
|
|
@@ -1956,20 +2200,15 @@ export default {
|
|
|
1956
2200
|
|
|
1957
2201
|
this._clearCollisionObbDebugGroup(false);
|
|
1958
2202
|
const pairMap = this._buildCollisionPairMap();
|
|
1959
|
-
if (
|
|
2203
|
+
if (!this._boxIndex || this._boxIndex.size === 0) return;
|
|
1960
2204
|
|
|
1961
|
-
|
|
1962
|
-
const box =
|
|
1963
|
-
this._boxIndex && this._boxIndex.has(String(modelId))
|
|
1964
|
-
? this._boxIndex.get(String(modelId))
|
|
1965
|
-
: this._boxIndex && this._boxIndex.has(modelId)
|
|
1966
|
-
? this._boxIndex.get(modelId)
|
|
1967
|
-
: null;
|
|
2205
|
+
this._boxIndex.forEach((box, modelId) => {
|
|
1968
2206
|
if (!this._isCollisionBoxAvailable(box)) return;
|
|
1969
2207
|
|
|
1970
2208
|
const obbs = this._getCollisionObbs(box);
|
|
1971
2209
|
if (!obbs.length) return;
|
|
1972
2210
|
|
|
2211
|
+
const relatedIds = pairMap.get(String(modelId));
|
|
1973
2212
|
const isColliding = relatedIds && relatedIds.size > 0;
|
|
1974
2213
|
const color = isColliding ? 0xff3b30 : 0x36cfc9;
|
|
1975
2214
|
for (let i = 0; i < obbs.length; i++) {
|
|
@@ -1995,6 +2234,165 @@ export default {
|
|
|
1995
2234
|
this.hideCollisionObbDebug();
|
|
1996
2235
|
}
|
|
1997
2236
|
},
|
|
2237
|
+
_getOcclusionDebugGroup() {
|
|
2238
|
+
if (!this.scene) return null;
|
|
2239
|
+
if (this.occlusionBoundsDebugGroup && this.occlusionBoundsDebugGroup.parent === this.scene) {
|
|
2240
|
+
return this.occlusionBoundsDebugGroup;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
const group = new this.THREE.Group();
|
|
2244
|
+
group.name = OCCLUSION_DEBUG_GROUP_NAME;
|
|
2245
|
+
group.userData.transformControlHelper = true;
|
|
2246
|
+
group.userData.occlusionBoundsDebugHelper = true;
|
|
2247
|
+
this.scene.add(group);
|
|
2248
|
+
this.occlusionBoundsDebugGroup = group;
|
|
2249
|
+
return group;
|
|
2250
|
+
},
|
|
2251
|
+
_createOcclusionFlag1LineSegments(box, color) {
|
|
2252
|
+
const userData = box && box.userData ? box.userData : {};
|
|
2253
|
+
const vertices = userData.obbData;
|
|
2254
|
+
if (!vertices || vertices.length < 9) return null;
|
|
2255
|
+
|
|
2256
|
+
const sourceGeometry = new this.THREE.BufferGeometry();
|
|
2257
|
+
sourceGeometry.setAttribute(
|
|
2258
|
+
'position',
|
|
2259
|
+
new this.THREE.BufferAttribute(new Float32Array(vertices), 3)
|
|
2260
|
+
);
|
|
2261
|
+
|
|
2262
|
+
const indices = userData.indices;
|
|
2263
|
+
if (indices && indices.length >= 3) {
|
|
2264
|
+
if (indices.isBufferAttribute) {
|
|
2265
|
+
sourceGeometry.setIndex(indices);
|
|
2266
|
+
} else if (ArrayBuffer.isView(indices)) {
|
|
2267
|
+
sourceGeometry.setIndex(new this.THREE.BufferAttribute(indices, 1));
|
|
2268
|
+
} else {
|
|
2269
|
+
sourceGeometry.setIndex(indices);
|
|
2270
|
+
}
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
let wireframeGeometry = null;
|
|
2274
|
+
try {
|
|
2275
|
+
// flag=1 对应真实遮挡网格,必须按顶点和三角形索引绘制,不能退化成 AABB/OBB。
|
|
2276
|
+
wireframeGeometry = new this.THREE.WireframeGeometry(sourceGeometry);
|
|
2277
|
+
} catch (error) {
|
|
2278
|
+
console.warn('flag=1 遮挡调试线框生成失败', error);
|
|
2279
|
+
}
|
|
2280
|
+
sourceGeometry.dispose();
|
|
2281
|
+
if (!wireframeGeometry || wireframeGeometry.getAttribute('position').count === 0) {
|
|
2282
|
+
if (wireframeGeometry) wireframeGeometry.dispose();
|
|
2283
|
+
return null;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
const material = new this.THREE.LineBasicMaterial({
|
|
2287
|
+
color,
|
|
2288
|
+
transparent: true,
|
|
2289
|
+
opacity: 0.95,
|
|
2290
|
+
depthTest: false,
|
|
2291
|
+
depthWrite: false,
|
|
2292
|
+
toneMapped: false,
|
|
2293
|
+
});
|
|
2294
|
+
const lineSegments = new this.THREE.LineSegments(wireframeGeometry, material);
|
|
2295
|
+
const transformMatrix = new this.THREE.Matrix4();
|
|
2296
|
+
const matrixArr = userData.matrix;
|
|
2297
|
+
if (matrixArr && matrixArr.length >= 16) {
|
|
2298
|
+
transformMatrix.fromArray(matrixArr);
|
|
2299
|
+
}
|
|
2300
|
+
if (this.bizToThreeMatrix) {
|
|
2301
|
+
transformMatrix.premultiply(this.bizToThreeMatrix);
|
|
2302
|
+
}
|
|
2303
|
+
lineSegments.matrixAutoUpdate = false;
|
|
2304
|
+
lineSegments.matrix.copy(transformMatrix);
|
|
2305
|
+
lineSegments.matrixWorldNeedsUpdate = true;
|
|
2306
|
+
lineSegments.renderOrder = 998;
|
|
2307
|
+
lineSegments.userData.transformControlHelper = true;
|
|
2308
|
+
lineSegments.userData.occlusionBoundsDebugHelper = true;
|
|
2309
|
+
lineSegments.userData.occlusionFlag = 1;
|
|
2310
|
+
return lineSegments;
|
|
2311
|
+
},
|
|
2312
|
+
_createOcclusionBoxLineSegments(obb, color, flag) {
|
|
2313
|
+
const lineSegments = this._createCollisionObbLineSegments(obb, color);
|
|
2314
|
+
if (!lineSegments) return null;
|
|
2315
|
+
|
|
2316
|
+
lineSegments.renderOrder = 998;
|
|
2317
|
+
lineSegments.userData.collisionObbDebugHelper = false;
|
|
2318
|
+
lineSegments.userData.occlusionBoundsDebugHelper = true;
|
|
2319
|
+
lineSegments.userData.occlusionFlag = flag;
|
|
2320
|
+
return lineSegments;
|
|
2321
|
+
},
|
|
2322
|
+
_clearOcclusionBoundsDebugGroup(removeGroup = false) {
|
|
2323
|
+
const group = this.occlusionBoundsDebugGroup;
|
|
2324
|
+
if (!group) return;
|
|
2325
|
+
|
|
2326
|
+
while (group.children.length > 0) {
|
|
2327
|
+
const child = group.children[group.children.length - 1];
|
|
2328
|
+
group.remove(child);
|
|
2329
|
+
if (child.geometry) child.geometry.dispose();
|
|
2330
|
+
if (Array.isArray(child.material)) {
|
|
2331
|
+
child.material.forEach(material => material && material.dispose());
|
|
2332
|
+
} else if (child.material) {
|
|
2333
|
+
child.material.dispose();
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
|
|
2337
|
+
if (removeGroup && this.scene && group.parent === this.scene) {
|
|
2338
|
+
this.scene.remove(group);
|
|
2339
|
+
this.occlusionBoundsDebugGroup = null;
|
|
2340
|
+
} else if (!this.scene) {
|
|
2341
|
+
this.occlusionBoundsDebugGroup = null;
|
|
2342
|
+
}
|
|
2343
|
+
},
|
|
2344
|
+
hideOcclusionBoundsDebug() {
|
|
2345
|
+
this._clearOcclusionBoundsDebugGroup();
|
|
2346
|
+
this.occlusionBoundsDebugEnabled = false;
|
|
2347
|
+
},
|
|
2348
|
+
refreshOcclusionBoundsDebug() {
|
|
2349
|
+
if (!this.occlusionBoundsDebugEnabled) return;
|
|
2350
|
+
const group = this._getOcclusionDebugGroup();
|
|
2351
|
+
if (!group) return;
|
|
2352
|
+
|
|
2353
|
+
this._clearOcclusionBoundsDebugGroup(false);
|
|
2354
|
+
if (!this._boxIndex || this._boxIndex.size === 0) return;
|
|
2355
|
+
|
|
2356
|
+
this._boxIndex.forEach((box, modelId) => {
|
|
2357
|
+
if (!this._isCollisionBoxAvailable(box) || !this.shouldApplyOcclusionByBox(box)) return;
|
|
2358
|
+
|
|
2359
|
+
const userData = box.userData || {};
|
|
2360
|
+
const flag = Number(userData.flag) || 1;
|
|
2361
|
+
const color = OCCLUSION_DEBUG_FLAG_COLORS[flag];
|
|
2362
|
+
if (!color) return;
|
|
2363
|
+
|
|
2364
|
+
if (flag === 1) {
|
|
2365
|
+
const lineSegments = this._createOcclusionFlag1LineSegments(box, color);
|
|
2366
|
+
if (!lineSegments) return;
|
|
2367
|
+
lineSegments.name = `${OCCLUSION_DEBUG_GROUP_NAME}:${modelId}:flag-1`;
|
|
2368
|
+
lineSegments.userData.modelId = modelId;
|
|
2369
|
+
group.add(lineSegments);
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
const obbs = this._getCollisionObbs(box);
|
|
2374
|
+
for (let i = 0; i < obbs.length; i++) {
|
|
2375
|
+
const lineSegments = this._createOcclusionBoxLineSegments(obbs[i], color, flag);
|
|
2376
|
+
if (!lineSegments) continue;
|
|
2377
|
+
lineSegments.name = `${OCCLUSION_DEBUG_GROUP_NAME}:${modelId}:flag-${flag}:${i}`;
|
|
2378
|
+
lineSegments.userData.modelId = modelId;
|
|
2379
|
+
group.add(lineSegments);
|
|
2380
|
+
}
|
|
2381
|
+
});
|
|
2382
|
+
},
|
|
2383
|
+
showOcclusionBoundsDebug() {
|
|
2384
|
+
this.occlusionBoundsDebugEnabled = true;
|
|
2385
|
+
this.refreshOcclusionBoundsDebug();
|
|
2386
|
+
},
|
|
2387
|
+
toggleOcclusionBoundsDebug(forceVisible) {
|
|
2388
|
+
const nextVisible =
|
|
2389
|
+
typeof forceVisible === 'boolean' ? forceVisible : !this.occlusionBoundsDebugEnabled;
|
|
2390
|
+
if (nextVisible) {
|
|
2391
|
+
this.showOcclusionBoundsDebug();
|
|
2392
|
+
} else {
|
|
2393
|
+
this.hideOcclusionBoundsDebug();
|
|
2394
|
+
}
|
|
2395
|
+
},
|
|
1998
2396
|
_getObbAxisInfo(obb) {
|
|
1999
2397
|
const xAxis = new this.THREE.Vector3();
|
|
2000
2398
|
const yAxis = new this.THREE.Vector3();
|
|
@@ -2064,16 +2462,15 @@ export default {
|
|
|
2064
2462
|
const obbsB = this._getCollisionObbs(boxB);
|
|
2065
2463
|
for (let i = 0; i < obbsA.length; i++) {
|
|
2066
2464
|
for (let j = 0; j < obbsB.length; j++) {
|
|
2067
|
-
if (this._getObbMinPenetration(obbsA[i], obbsB[j])
|
|
2465
|
+
if (this._getObbMinPenetration(obbsA[i], obbsB[j]) >= COLLISION_CONTACT_EPSILON) {
|
|
2068
2466
|
return true;
|
|
2069
2467
|
}
|
|
2070
2468
|
}
|
|
2071
2469
|
}
|
|
2072
2470
|
return false;
|
|
2073
2471
|
},
|
|
2074
|
-
|
|
2472
|
+
_queryOctreeByBoxWithRoot(root, box) {
|
|
2075
2473
|
const results = [];
|
|
2076
|
-
const root = this._octree;
|
|
2077
2474
|
if (!root || !this._isCollisionBoxAvailable(box)) return results;
|
|
2078
2475
|
|
|
2079
2476
|
const stack = [root];
|
|
@@ -2099,25 +2496,88 @@ export default {
|
|
|
2099
2496
|
|
|
2100
2497
|
return results;
|
|
2101
2498
|
},
|
|
2102
|
-
|
|
2499
|
+
_queryOctreeByBox(box) {
|
|
2500
|
+
return this._queryOctreeByBoxWithRoot(this._octree, box);
|
|
2501
|
+
},
|
|
2502
|
+
getOctreeCollisionPairs(options = {}) {
|
|
2103
2503
|
const pairs = [];
|
|
2104
2504
|
if (!this._boxIndex || this._boxIndex.size === 0) return { pairs };
|
|
2105
2505
|
|
|
2106
|
-
|
|
2107
|
-
|
|
2506
|
+
const normalizeIds = value =>
|
|
2507
|
+
Array.isArray(value)
|
|
2508
|
+
? Array.from(
|
|
2509
|
+
new Set(
|
|
2510
|
+
value
|
|
2511
|
+
.map(item => (item === null || item === undefined ? '' : String(item)))
|
|
2512
|
+
.filter(item => item !== '')
|
|
2513
|
+
)
|
|
2514
|
+
)
|
|
2515
|
+
: [];
|
|
2516
|
+
|
|
2517
|
+
const getEntriesByIds = ids => {
|
|
2518
|
+
const entries = [];
|
|
2519
|
+
const idSet = new Set(ids);
|
|
2520
|
+
this._boxIndex.forEach((box, id) => {
|
|
2521
|
+
const key = String(id);
|
|
2522
|
+
if (idSet.has(key)) {
|
|
2523
|
+
entries.push({
|
|
2524
|
+
id: key,
|
|
2525
|
+
box,
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
});
|
|
2529
|
+
return entries;
|
|
2530
|
+
};
|
|
2531
|
+
|
|
2532
|
+
const sourceIds = normalizeIds(options.sourceIds);
|
|
2533
|
+
const targetIds = normalizeIds(options.targetIds);
|
|
2534
|
+
const includeIds = Array.isArray(options.includeIds) ? normalizeIds(options.includeIds) : [];
|
|
2535
|
+
|
|
2536
|
+
let root = this._octree;
|
|
2537
|
+
let sourceEntries = [];
|
|
2538
|
+
let dedupePairKeySet = null;
|
|
2539
|
+
|
|
2540
|
+
if (sourceIds.length > 0 && targetIds.length > 0) {
|
|
2541
|
+
const targetEntries = getEntriesByIds(targetIds);
|
|
2542
|
+
sourceEntries = getEntriesByIds(sourceIds);
|
|
2543
|
+
if (sourceEntries.length === 0 || targetEntries.length === 0) return { pairs };
|
|
2544
|
+
root = this._buildCollisionOctreeFromEntries(targetEntries);
|
|
2545
|
+
if (!root) return { pairs };
|
|
2546
|
+
dedupePairKeySet = new Set();
|
|
2547
|
+
} else if (includeIds.length > 0) {
|
|
2548
|
+
sourceEntries = getEntriesByIds(includeIds);
|
|
2549
|
+
if (sourceEntries.length === 0) return { pairs };
|
|
2550
|
+
root = this._buildCollisionOctreeFromEntries(sourceEntries);
|
|
2551
|
+
if (!root) return { pairs };
|
|
2552
|
+
} else {
|
|
2553
|
+
if (!this._octree) {
|
|
2554
|
+
this.buildOctreeFromBoxIndex();
|
|
2555
|
+
}
|
|
2556
|
+
if (!this._octree) return { pairs };
|
|
2557
|
+
this._boxIndex.forEach((box, id) => {
|
|
2558
|
+
sourceEntries.push({
|
|
2559
|
+
id: String(id),
|
|
2560
|
+
box,
|
|
2561
|
+
});
|
|
2562
|
+
});
|
|
2108
2563
|
}
|
|
2109
|
-
if (!this._octree) return { pairs };
|
|
2110
2564
|
|
|
2111
|
-
|
|
2565
|
+
sourceEntries.forEach(({ id: idA, box: boxA }) => {
|
|
2112
2566
|
if (!this._isCollisionBoxAvailable(boxA)) return;
|
|
2113
2567
|
|
|
2114
|
-
const hits = this.
|
|
2568
|
+
const hits = this._queryOctreeByBoxWithRoot(root, boxA);
|
|
2115
2569
|
for (let i = 0; i < hits.length; i++) {
|
|
2116
2570
|
const item = hits[i];
|
|
2117
2571
|
if (!item || String(item.id) === String(idA)) continue;
|
|
2118
2572
|
const boxB = item.box;
|
|
2119
2573
|
if (!this._isCollisionBoxMatched(boxA, boxB)) continue;
|
|
2120
2574
|
|
|
2575
|
+
if (dedupePairKeySet) {
|
|
2576
|
+
const pairKey = `${String(idA)}__${String(item.id)}`;
|
|
2577
|
+
if (dedupePairKeySet.has(pairKey)) continue;
|
|
2578
|
+
dedupePairKeySet.add(pairKey);
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2121
2581
|
pairs.push({
|
|
2122
2582
|
idA: this._formatCollisionPairId(idA),
|
|
2123
2583
|
idB: this._formatCollisionPairId(item.id),
|
|
@@ -2571,7 +3031,8 @@ export default {
|
|
|
2571
3031
|
/**
|
|
2572
3032
|
* 执行视椎体裁切,清理视椎体外的InstancedMesh实例
|
|
2573
3033
|
*/
|
|
2574
|
-
async performFrustumCulling() {
|
|
3034
|
+
async performFrustumCulling(options = {}) {
|
|
3035
|
+
const skipUnload = !!(options && options.skipUnload);
|
|
2575
3036
|
const modelState = this.noObserver
|
|
2576
3037
|
? this.noObserver.modelStateManager
|
|
2577
3038
|
: this.modelStateManager;
|
|
@@ -2613,7 +3074,12 @@ export default {
|
|
|
2613
3074
|
visibleIdSet.add(this.formatModelId(hits[i].modelId));
|
|
2614
3075
|
continue;
|
|
2615
3076
|
}
|
|
2616
|
-
|
|
3077
|
+
this.appendOcclusionCandidates(
|
|
3078
|
+
candidates,
|
|
3079
|
+
hits[i].modelId,
|
|
3080
|
+
hits[i].box,
|
|
3081
|
+
globalFrustum
|
|
3082
|
+
);
|
|
2617
3083
|
}
|
|
2618
3084
|
} else if (this._boxIndex && this._boxIndex.size > 0) {
|
|
2619
3085
|
this._boxIndex.forEach((box, modelId) => {
|
|
@@ -2624,7 +3090,7 @@ export default {
|
|
|
2624
3090
|
visibleIdSet.add(this.formatModelId(modelId));
|
|
2625
3091
|
return;
|
|
2626
3092
|
}
|
|
2627
|
-
|
|
3093
|
+
this.appendOcclusionCandidates(candidates, modelId, box, globalFrustum);
|
|
2628
3094
|
}
|
|
2629
3095
|
});
|
|
2630
3096
|
}
|
|
@@ -2740,6 +3206,7 @@ export default {
|
|
|
2740
3206
|
const tBuild0 = performance.now();
|
|
2741
3207
|
|
|
2742
3208
|
const flag1Items = [];
|
|
3209
|
+
const flag2Items = [];
|
|
2743
3210
|
const flag3Items = [];
|
|
2744
3211
|
|
|
2745
3212
|
let globalIdx = 1;
|
|
@@ -2753,6 +3220,8 @@ export default {
|
|
|
2753
3220
|
const flag = (c.box.userData && c.box.userData.flag) || 1;
|
|
2754
3221
|
if (flag === 3 && c.box.userData && c.box.userData.obb) {
|
|
2755
3222
|
flag3Items.push({ c, idx });
|
|
3223
|
+
} else if (flag === 2) {
|
|
3224
|
+
flag2Items.push({ c, idx });
|
|
2756
3225
|
} else if (flag === 1) {
|
|
2757
3226
|
flag1Items.push({ c, idx });
|
|
2758
3227
|
}
|
|
@@ -2769,7 +3238,8 @@ export default {
|
|
|
2769
3238
|
const _tempMatA = occObjs._tempMatA || (occObjs._tempMatA = new this.THREE.Matrix4());
|
|
2770
3239
|
|
|
2771
3240
|
let boxes = occObjs.boxes;
|
|
2772
|
-
const
|
|
3241
|
+
const boxItems = flag3Items.concat(flag2Items);
|
|
3242
|
+
const boxCount = boxItems.length;
|
|
2773
3243
|
if (boxCount > 0) {
|
|
2774
3244
|
const needCapacity = boxCount;
|
|
2775
3245
|
const capacity = boxes && typeof boxes.capacity === 'number' ? boxes.capacity : 0;
|
|
@@ -2799,19 +3269,26 @@ export default {
|
|
|
2799
3269
|
}
|
|
2800
3270
|
boxes.mesh.count = boxCount;
|
|
2801
3271
|
for (let i = 0; i < boxCount; i++) {
|
|
2802
|
-
const item =
|
|
2803
|
-
const { matrix, halfSize } = item.c.box.userData.obb;
|
|
3272
|
+
const item = boxItems[i];
|
|
2804
3273
|
const idx = item.idx;
|
|
2805
3274
|
const r = (idx & 255) / 255;
|
|
2806
3275
|
const g = ((idx >> 8) & 255) / 255;
|
|
2807
3276
|
const b = ((idx >> 16) & 255) / 255;
|
|
2808
3277
|
_tempColor.setRGB(r, g, b);
|
|
2809
3278
|
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
_tempMatrix.
|
|
3279
|
+
const obb = item.c.box.userData && item.c.box.userData.obb;
|
|
3280
|
+
if (obb) {
|
|
3281
|
+
_tempScale.copy(obb.halfSize).multiplyScalar(2);
|
|
3282
|
+
_tempMatrix.copy(obb.matrix);
|
|
3283
|
+
_tempMatrix.scale(_tempScale);
|
|
3284
|
+
if (typeof this.bizToThreeMatrix !== 'undefined' && this.bizToThreeMatrix) {
|
|
3285
|
+
_tempMatrix.premultiply(this.bizToThreeMatrix);
|
|
3286
|
+
}
|
|
3287
|
+
} else {
|
|
3288
|
+
item.c.box.getSize(_tempScale);
|
|
3289
|
+
item.c.box.getCenter(_tempVec3);
|
|
3290
|
+
_tempMatrix.makeTranslation(_tempVec3.x, _tempVec3.y, _tempVec3.z);
|
|
3291
|
+
_tempMatrix.scale(_tempScale);
|
|
2815
3292
|
}
|
|
2816
3293
|
|
|
2817
3294
|
boxes.mesh.setMatrixAt(i, _tempMatrix);
|
|
@@ -3254,12 +3731,12 @@ export default {
|
|
|
3254
3731
|
this.renderer.toneMapping = prevToneMapping;
|
|
3255
3732
|
} catch (e) {
|
|
3256
3733
|
for (let i = 0; i < candidates.length; i++) {
|
|
3257
|
-
visibleIdSet.add(candidates[i].modelId);
|
|
3734
|
+
visibleIdSet.add(this.formatModelId(candidates[i].modelId));
|
|
3258
3735
|
}
|
|
3259
3736
|
}
|
|
3260
3737
|
} else {
|
|
3261
3738
|
for (let i = 0; i < candidates.length; i++) {
|
|
3262
|
-
visibleIdSet.add(candidates[i].modelId);
|
|
3739
|
+
visibleIdSet.add(this.formatModelId(candidates[i].modelId));
|
|
3263
3740
|
}
|
|
3264
3741
|
}
|
|
3265
3742
|
visibleIds.length = 0;
|
|
@@ -3268,37 +3745,43 @@ export default {
|
|
|
3268
3745
|
const parentsToCheck = new Set();
|
|
3269
3746
|
this.scene.traverse(child => {
|
|
3270
3747
|
if (!child.isInstancedMesh) return;
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
? child.parent.userData.instancesMap
|
|
3280
|
-
: null;
|
|
3748
|
+
const instancesMap =
|
|
3749
|
+
child && child.userData && child.userData.instancesMap instanceof Map
|
|
3750
|
+
? child.userData.instancesMap
|
|
3751
|
+
: child.parent &&
|
|
3752
|
+
child.parent.userData &&
|
|
3753
|
+
child.parent.userData.instancesMap instanceof Map
|
|
3754
|
+
? child.parent.userData.instancesMap
|
|
3755
|
+
: null;
|
|
3281
3756
|
|
|
3757
|
+
// 多实例复用同一个 InstancedMesh 时,仅在全部实例都不可见时整体卸载。
|
|
3758
|
+
if (child.count >= 2) {
|
|
3282
3759
|
if (instancesMap) {
|
|
3283
3760
|
let allInactive = true;
|
|
3284
3761
|
const modelIds = Array.from(instancesMap.keys());
|
|
3762
|
+
let unloadModelId = null;
|
|
3285
3763
|
|
|
3286
3764
|
// 第一遍遍历:检查是否有任何一个实例处于活跃状态(在视锥体内且可见)
|
|
3287
3765
|
for (const modelId of modelIds) {
|
|
3288
3766
|
// 检查已取消勾选模型进行卸载
|
|
3289
|
-
const documentId = modelId.split(':')[1];
|
|
3290
|
-
if (!this.noObserver.documentModelIds.get(documentId)) {
|
|
3291
|
-
|
|
3292
|
-
|
|
3767
|
+
const documentId = String(modelId).split(':')[1];
|
|
3768
|
+
if (documentId && !this.noObserver.documentModelIds.get(documentId)) {
|
|
3769
|
+
if (!skipUnload) this.setInstanceCullingVisible(child, modelId, false);
|
|
3770
|
+
unloadModelId = modelId;
|
|
3771
|
+
continue;
|
|
3293
3772
|
}
|
|
3294
3773
|
|
|
3295
|
-
|
|
3774
|
+
const formattedModelId = this.formatModelId(modelId);
|
|
3775
|
+
if (
|
|
3776
|
+
bypassList &&
|
|
3777
|
+
bypassList.size > 0 &&
|
|
3778
|
+
(bypassList.has(modelId) || bypassList.has(formattedModelId))
|
|
3779
|
+
) {
|
|
3780
|
+
if (!skipUnload) this.setInstanceCullingVisible(child, modelId, true);
|
|
3781
|
+
allInactive = false;
|
|
3782
|
+
continue;
|
|
3783
|
+
}
|
|
3296
3784
|
|
|
3297
|
-
const instanceInfo = instancesMap.get(modelId);
|
|
3298
|
-
const instanceIndex =
|
|
3299
|
-
instanceInfo && typeof instanceInfo.instanceIndex === 'number'
|
|
3300
|
-
? instanceInfo.instanceIndex
|
|
3301
|
-
: null;
|
|
3302
3785
|
const box =
|
|
3303
3786
|
this._boxIndex && this._boxIndex.has(String(modelId))
|
|
3304
3787
|
? this._boxIndex.get(String(modelId))
|
|
@@ -3306,16 +3789,19 @@ export default {
|
|
|
3306
3789
|
? this._boxIndex.get(modelId)
|
|
3307
3790
|
: null;
|
|
3308
3791
|
if (!this.shouldApplyOcclusionByBox(box)) {
|
|
3792
|
+
if (!skipUnload) this.setInstanceCullingVisible(child, modelId, true);
|
|
3309
3793
|
allInactive = false;
|
|
3794
|
+
toLoadSet.delete(formattedModelId);
|
|
3310
3795
|
continue;
|
|
3311
3796
|
}
|
|
3312
3797
|
|
|
3313
|
-
const
|
|
3314
|
-
|
|
3315
|
-
// console.log('modelId', modelId, 'inFrustum', inFrustum, 'modelInVisible', modelInVisible)
|
|
3798
|
+
const modelInVisible = visibleIdSet.has(formattedModelId);
|
|
3799
|
+
toLoadSet.delete(formattedModelId);
|
|
3316
3800
|
|
|
3317
|
-
//
|
|
3318
|
-
|
|
3801
|
+
// 可见集合已经聚合了同一模型的全部图元,任一图元可见时都保留该模型实例。
|
|
3802
|
+
const isActive = modelInVisible;
|
|
3803
|
+
if (!skipUnload) this.setInstanceCullingVisible(child, modelId, isActive);
|
|
3804
|
+
if (isActive) {
|
|
3319
3805
|
allInactive = false;
|
|
3320
3806
|
}
|
|
3321
3807
|
}
|
|
@@ -3329,20 +3815,24 @@ export default {
|
|
|
3329
3815
|
? this._boxIndex.get(modelId)
|
|
3330
3816
|
: null;
|
|
3331
3817
|
if (!this.shouldApplyOcclusionByBox(box)) {
|
|
3332
|
-
|
|
3333
|
-
toLoadSet.delete(modelId);
|
|
3334
|
-
}
|
|
3818
|
+
toLoadSet.delete(this.formatModelId(modelId));
|
|
3335
3819
|
continue;
|
|
3336
3820
|
}
|
|
3337
3821
|
// 无论是否卸载,只要模型已在场景中,就从待加载集合中移除
|
|
3338
|
-
|
|
3339
|
-
toLoadSet.delete(modelId);
|
|
3340
|
-
}
|
|
3822
|
+
toLoadSet.delete(this.formatModelId(modelId));
|
|
3341
3823
|
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3824
|
+
if (!unloadModelId) unloadModelId = modelId;
|
|
3825
|
+
}
|
|
3826
|
+
|
|
3827
|
+
// 仅当所有实例都不活跃时,才整体卸载一次,避免重复释放同一网格。
|
|
3828
|
+
if (
|
|
3829
|
+
!skipUnload &&
|
|
3830
|
+
!this.firstPerSign &&
|
|
3831
|
+
!this.roaming &&
|
|
3832
|
+
allInactive &&
|
|
3833
|
+
unloadModelId !== null
|
|
3834
|
+
) {
|
|
3835
|
+
toUnload.push({ modelId: unloadModelId, child });
|
|
3346
3836
|
}
|
|
3347
3837
|
}
|
|
3348
3838
|
// if (toLoadSet.has(modelId)) {
|
|
@@ -3351,10 +3841,18 @@ export default {
|
|
|
3351
3841
|
return;
|
|
3352
3842
|
}
|
|
3353
3843
|
const modelId =
|
|
3354
|
-
|
|
3844
|
+
instancesMap && instancesMap.size > 0
|
|
3845
|
+
? instancesMap.keys().next().value
|
|
3846
|
+
: child.parent && child.parent.userData && child.parent.userData.instanceId
|
|
3355
3847
|
? child.parent.userData.instanceId
|
|
3356
3848
|
: child.uuid;
|
|
3357
|
-
|
|
3849
|
+
const formattedModelId = this.formatModelId(modelId);
|
|
3850
|
+
if (
|
|
3851
|
+
bypassList &&
|
|
3852
|
+
bypassList.size > 0 &&
|
|
3853
|
+
(bypassList.has(modelId) || bypassList.has(formattedModelId))
|
|
3854
|
+
)
|
|
3855
|
+
return;
|
|
3358
3856
|
const box =
|
|
3359
3857
|
this._boxIndex && this._boxIndex.has(String(modelId))
|
|
3360
3858
|
? this._boxIndex.get(String(modelId))
|
|
@@ -3362,18 +3860,13 @@ export default {
|
|
|
3362
3860
|
? this._boxIndex.get(modelId)
|
|
3363
3861
|
: null;
|
|
3364
3862
|
if (!this.shouldApplyOcclusionByBox(box)) {
|
|
3365
|
-
|
|
3366
|
-
toLoadSet.delete(modelId);
|
|
3367
|
-
}
|
|
3863
|
+
toLoadSet.delete(formattedModelId);
|
|
3368
3864
|
return;
|
|
3369
3865
|
}
|
|
3370
3866
|
|
|
3371
|
-
const
|
|
3372
|
-
|
|
3373
|
-
if (modelInVisible) {
|
|
3374
|
-
toLoadSet.delete(modelId);
|
|
3375
|
-
}
|
|
3376
|
-
if (!this.firstPerSign && !this.roaming && (!inFrustum || !modelInVisible)) {
|
|
3867
|
+
const modelInVisible = visibleIdSet.has(formattedModelId);
|
|
3868
|
+
toLoadSet.delete(formattedModelId);
|
|
3869
|
+
if (!skipUnload && !this.firstPerSign && !this.roaming && !modelInVisible) {
|
|
3377
3870
|
toUnload.push({ modelId, child });
|
|
3378
3871
|
}
|
|
3379
3872
|
});
|
|
@@ -3491,9 +3984,15 @@ export default {
|
|
|
3491
3984
|
* @param {Object} config - 配置项
|
|
3492
3985
|
* @param {Object} config.modelApi - 模型API接口
|
|
3493
3986
|
* @param {string} [config.projectId] - 项目ID
|
|
3987
|
+
* @param {Object} [config.cameraControlMoveObserver] - 普通视角运动中触发加载的配置
|
|
3988
|
+
* @param {boolean} [config.cameraControlMoveObserver.enabled=false] - 是否开启普通视角运动中加载
|
|
3989
|
+
* @param {number} [config.cameraControlMoveObserver.interval=500] - 触发间隔,单位毫秒
|
|
3990
|
+
* @param {boolean} [config.enableCameraControlMoveObserver=false] - 是否开启普通视角运动中加载
|
|
3991
|
+
* @param {number} [config.cameraControlMoveInterval=500] - 触发间隔,单位毫秒
|
|
3494
3992
|
*/
|
|
3495
3993
|
init(config = {}) {
|
|
3496
3994
|
console.log('init');
|
|
3995
|
+
this.setCameraControlMoveObserverConfig(config);
|
|
3497
3996
|
this.initStreamLoader(config.modelApi, config);
|
|
3498
3997
|
},
|
|
3499
3998
|
|
|
@@ -3920,6 +4419,124 @@ export default {
|
|
|
3920
4419
|
this.noObserver.isObserverEnabled = enabled;
|
|
3921
4420
|
},
|
|
3922
4421
|
|
|
4422
|
+
setCameraControlMoveObserverConfig(config = {}) {
|
|
4423
|
+
if (!this.noObserver) return;
|
|
4424
|
+
const nestedConfig = config.cameraControlMoveObserver || {};
|
|
4425
|
+
const enabled =
|
|
4426
|
+
typeof nestedConfig.enabled === 'boolean'
|
|
4427
|
+
? nestedConfig.enabled
|
|
4428
|
+
: typeof config.enableCameraControlMoveObserver === 'boolean'
|
|
4429
|
+
? config.enableCameraControlMoveObserver
|
|
4430
|
+
: false;
|
|
4431
|
+
const rawInterval =
|
|
4432
|
+
nestedConfig.interval !== undefined
|
|
4433
|
+
? nestedConfig.interval
|
|
4434
|
+
: config.cameraControlMoveInterval;
|
|
4435
|
+
const interval = Number(rawInterval);
|
|
4436
|
+
const safeInterval = Number.isFinite(interval) && interval > 0 ? interval : 500;
|
|
4437
|
+
|
|
4438
|
+
this.noObserver.cameraControlMoveObserver = {
|
|
4439
|
+
enabled,
|
|
4440
|
+
interval: safeInterval,
|
|
4441
|
+
};
|
|
4442
|
+
this.resetCameraControlMoveSnapshot(false);
|
|
4443
|
+
},
|
|
4444
|
+
|
|
4445
|
+
isCameraControlMoveObserverEnabled() {
|
|
4446
|
+
const config = this.noObserver && this.noObserver.cameraControlMoveObserver;
|
|
4447
|
+
return !!(config && config.enabled);
|
|
4448
|
+
},
|
|
4449
|
+
|
|
4450
|
+
getCameraControlTarget(target) {
|
|
4451
|
+
if (!target || !this.cameraControls) return false;
|
|
4452
|
+
if (typeof this.cameraControls.getTarget === 'function') {
|
|
4453
|
+
this.cameraControls.getTarget(target);
|
|
4454
|
+
return true;
|
|
4455
|
+
}
|
|
4456
|
+
if (this.cameraControls._target) {
|
|
4457
|
+
target.copy(this.cameraControls._target);
|
|
4458
|
+
return true;
|
|
4459
|
+
}
|
|
4460
|
+
return false;
|
|
4461
|
+
},
|
|
4462
|
+
|
|
4463
|
+
resetCameraControlMoveSnapshot(syncCurrent = false) {
|
|
4464
|
+
if (!this.noObserver) return;
|
|
4465
|
+
if (!this.noObserver._cameraControlMoveState) {
|
|
4466
|
+
this.noObserver._cameraControlMoveState = {
|
|
4467
|
+
position: new this.THREE.Vector3(),
|
|
4468
|
+
quaternion: new this.THREE.Quaternion(),
|
|
4469
|
+
target: new this.THREE.Vector3(),
|
|
4470
|
+
currentTarget: new this.THREE.Vector3(),
|
|
4471
|
+
initialized: false,
|
|
4472
|
+
hasTarget: false,
|
|
4473
|
+
};
|
|
4474
|
+
}
|
|
4475
|
+
const state = this.noObserver._cameraControlMoveState;
|
|
4476
|
+
if (!syncCurrent || !this.camera) {
|
|
4477
|
+
state.initialized = false;
|
|
4478
|
+
state.hasTarget = false;
|
|
4479
|
+
return;
|
|
4480
|
+
}
|
|
4481
|
+
|
|
4482
|
+
this.camera.updateMatrixWorld(true);
|
|
4483
|
+
state.position.copy(this.camera.position);
|
|
4484
|
+
state.quaternion.copy(this.camera.quaternion);
|
|
4485
|
+
state.hasTarget = this.getCameraControlTarget(state.target);
|
|
4486
|
+
state.initialized = true;
|
|
4487
|
+
},
|
|
4488
|
+
|
|
4489
|
+
observeCameraControlMove() {
|
|
4490
|
+
if (
|
|
4491
|
+
!this.noObserver ||
|
|
4492
|
+
!this.noObserver.isObserverEnabled ||
|
|
4493
|
+
!this.isCameraControlMoveObserverEnabled() ||
|
|
4494
|
+
!this.camera ||
|
|
4495
|
+
!this.cameraControls ||
|
|
4496
|
+
typeof this._cameraChangeObserver !== 'function'
|
|
4497
|
+
) {
|
|
4498
|
+
return;
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
const loadingState = this.noObserver.batchLoadingState || {};
|
|
4502
|
+
const interactionState = loadingState.interactionState || {};
|
|
4503
|
+
const wheelActive = !!interactionState.wheelTimeout;
|
|
4504
|
+
const controlActive = !!this.noObserver.isControlActive;
|
|
4505
|
+
if ((!controlActive && !wheelActive) || this.firstPerSign) {
|
|
4506
|
+
this.resetCameraControlMoveSnapshot(false);
|
|
4507
|
+
return;
|
|
4508
|
+
}
|
|
4509
|
+
|
|
4510
|
+
if (!this.noObserver._cameraControlMoveState) {
|
|
4511
|
+
this.resetCameraControlMoveSnapshot(true);
|
|
4512
|
+
return;
|
|
4513
|
+
}
|
|
4514
|
+
|
|
4515
|
+
const state = this.noObserver._cameraControlMoveState;
|
|
4516
|
+
if (!state.initialized) {
|
|
4517
|
+
this.resetCameraControlMoveSnapshot(true);
|
|
4518
|
+
return;
|
|
4519
|
+
}
|
|
4520
|
+
|
|
4521
|
+
this.camera.updateMatrixWorld(true);
|
|
4522
|
+
const hasTarget = this.getCameraControlTarget(state.currentTarget);
|
|
4523
|
+
const positionMoved = state.position.distanceToSquared(this.camera.position) > 1e-8;
|
|
4524
|
+
const quaternionMoved = 1 - Math.abs(state.quaternion.dot(this.camera.quaternion)) > 1e-10;
|
|
4525
|
+
const targetMoved =
|
|
4526
|
+
hasTarget !== state.hasTarget ||
|
|
4527
|
+
(hasTarget && state.target.distanceToSquared(state.currentTarget) > 1e-8);
|
|
4528
|
+
|
|
4529
|
+
if (!positionMoved && !quaternionMoved && !targetMoved) return;
|
|
4530
|
+
|
|
4531
|
+
state.position.copy(this.camera.position);
|
|
4532
|
+
state.quaternion.copy(this.camera.quaternion);
|
|
4533
|
+
state.hasTarget = hasTarget;
|
|
4534
|
+
if (hasTarget) {
|
|
4535
|
+
state.target.copy(state.currentTarget);
|
|
4536
|
+
}
|
|
4537
|
+
this._cameraChangeObserver('controlMove');
|
|
4538
|
+
},
|
|
4539
|
+
|
|
3923
4540
|
/**
|
|
3924
4541
|
* 初始化相机变更观察者 (CameraChangeObserver)
|
|
3925
4542
|
* 统一监听:用户交互(Wheel/Control)、程序化动画、API调用
|
|
@@ -3954,7 +4571,9 @@ export default {
|
|
|
3954
4571
|
// 执行视锥体裁剪
|
|
3955
4572
|
// 第一视角不裁切
|
|
3956
4573
|
if (this.modelStateManager.frustumCheckEnabled) {
|
|
3957
|
-
await this.performFrustumCulling(
|
|
4574
|
+
await this.performFrustumCulling({
|
|
4575
|
+
skipUnload: source === 'controlMove',
|
|
4576
|
+
});
|
|
3958
4577
|
}
|
|
3959
4578
|
|
|
3960
4579
|
// 执行流式加载请求
|
|
@@ -3966,11 +4585,19 @@ export default {
|
|
|
3966
4585
|
}
|
|
3967
4586
|
},
|
|
3968
4587
|
source => {
|
|
3969
|
-
//
|
|
4588
|
+
// 漫游、第一人称移动、普通视角拖拽/缩放中,使用节流策略避免持续运动被防抖吞掉。
|
|
3970
4589
|
// 避免在持续运动中因防抖重置定时器而无法触发
|
|
3971
4590
|
if (source === 'roam' || source === 'firstPersonMove') {
|
|
3972
4591
|
return { type: 'throttle', limit: 1000 };
|
|
3973
4592
|
}
|
|
4593
|
+
if (source === 'controlMove') {
|
|
4594
|
+
const config = this.noObserver && this.noObserver.cameraControlMoveObserver;
|
|
4595
|
+
const interval =
|
|
4596
|
+
config && Number.isFinite(config.interval) && config.interval > 0
|
|
4597
|
+
? config.interval
|
|
4598
|
+
: 500;
|
|
4599
|
+
return { type: 'throttle', limit: interval };
|
|
4600
|
+
}
|
|
3974
4601
|
return 300; // 默认防抖 300ms
|
|
3975
4602
|
}
|
|
3976
4603
|
);
|
|
@@ -3984,8 +4611,20 @@ export default {
|
|
|
3984
4611
|
this._wheelHandler = res => {
|
|
3985
4612
|
this.$emit('wheelStart', res);
|
|
3986
4613
|
|
|
3987
|
-
|
|
3988
|
-
this.
|
|
4614
|
+
const cameraControlMoveEnabled = this.isCameraControlMoveObserverEnabled();
|
|
4615
|
+
const loadingState = this.noObserver
|
|
4616
|
+
? this.noObserver.batchLoadingState
|
|
4617
|
+
: this.batchLoadingState;
|
|
4618
|
+
const wheelAlreadyActive =
|
|
4619
|
+
loadingState &&
|
|
4620
|
+
loadingState.interactionState &&
|
|
4621
|
+
loadingState.interactionState.wheelTimeout;
|
|
4622
|
+
if (cameraControlMoveEnabled && !wheelAlreadyActive) {
|
|
4623
|
+
this.resetCameraControlMoveSnapshot(true);
|
|
4624
|
+
} else if (!cameraControlMoveEnabled) {
|
|
4625
|
+
// 未开启普通视角运动加载时,保持原有交互中断流程。
|
|
4626
|
+
this.beginInteraction('wheel');
|
|
4627
|
+
}
|
|
3989
4628
|
|
|
3990
4629
|
// 设置滚轮交互的结束检测
|
|
3991
4630
|
this.scheduleWheelInteractionEnd(res, 100);
|
|
@@ -4027,9 +4666,12 @@ export default {
|
|
|
4027
4666
|
// 4. 监听器 - Controls (用户拖拽/操作)
|
|
4028
4667
|
this._onControlStart = res => {
|
|
4029
4668
|
this.noObserver.isControlActive = true;
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4669
|
+
if (this.isCameraControlMoveObserverEnabled()) {
|
|
4670
|
+
this.resetCameraControlMoveSnapshot(true);
|
|
4671
|
+
} else {
|
|
4672
|
+
// 未开启普通视角运动加载时,保持原有交互中断流程。
|
|
4673
|
+
this.beginInteraction('this.camera');
|
|
4674
|
+
}
|
|
4033
4675
|
this.$emit('controlStart', res);
|
|
4034
4676
|
|
|
4035
4677
|
// 记录鼠标按下时的位置
|
|
@@ -4046,8 +4688,10 @@ export default {
|
|
|
4046
4688
|
|
|
4047
4689
|
this.$emit('controlEnd', res);
|
|
4048
4690
|
|
|
4049
|
-
|
|
4050
|
-
|
|
4691
|
+
if (!this.isCameraControlMoveObserverEnabled()) {
|
|
4692
|
+
// 未开启普通视角运动加载时,保持原有交互中断流程。
|
|
4693
|
+
this.endInteraction('this.camera', res, { immediateResume: false });
|
|
4694
|
+
}
|
|
4051
4695
|
|
|
4052
4696
|
// 记录鼠标抬起时的位置并计算是否发生了拖拽
|
|
4053
4697
|
const event = res.originalEvent || window.event;
|
|
@@ -4076,6 +4720,9 @@ export default {
|
|
|
4076
4720
|
// 触发观察者
|
|
4077
4721
|
this._cameraChangeObserver('control');
|
|
4078
4722
|
}
|
|
4723
|
+
if (this.isCameraControlMoveObserverEnabled()) {
|
|
4724
|
+
this.resetCameraControlMoveSnapshot(false);
|
|
4725
|
+
}
|
|
4079
4726
|
};
|
|
4080
4727
|
|
|
4081
4728
|
this.cameraControls.addEventListener('controlstart', this._onControlStart);
|
|
@@ -4136,12 +4783,37 @@ export default {
|
|
|
4136
4783
|
},
|
|
4137
4784
|
// 动态设置视角滚轮的距离
|
|
4138
4785
|
setCameraConfig() {
|
|
4786
|
+
if (
|
|
4787
|
+
!this.sceneBoundingBox ||
|
|
4788
|
+
!this.sceneBoundingBox.isBox3 ||
|
|
4789
|
+
this.sceneBoundingBox.isEmpty()
|
|
4790
|
+
) {
|
|
4791
|
+
return;
|
|
4792
|
+
}
|
|
4139
4793
|
// let box3 = new this.THREE.Box3().setFromObject(this.sceneBoundingBox);
|
|
4140
4794
|
let size = new this.THREE.Vector3();
|
|
4141
4795
|
this.sceneBoundingBox.getSize(size);
|
|
4142
4796
|
const maxBorder = Math.max(size.x, size.y, size.z);
|
|
4797
|
+
if (isDebug) {
|
|
4798
|
+
const center = this.sceneBoundingBox.getCenter(new this.THREE.Vector3());
|
|
4799
|
+
console.log('sceneBoundingBox 调试信息', {
|
|
4800
|
+
size: {
|
|
4801
|
+
x: size.x,
|
|
4802
|
+
y: size.y,
|
|
4803
|
+
z: size.z,
|
|
4804
|
+
},
|
|
4805
|
+
center: {
|
|
4806
|
+
x: center.x,
|
|
4807
|
+
y: center.y,
|
|
4808
|
+
z: center.z,
|
|
4809
|
+
},
|
|
4810
|
+
maxBorder,
|
|
4811
|
+
});
|
|
4812
|
+
this.refreshSceneBoundingBoxHelper(this.sceneBoundingBoxDebugEnabled);
|
|
4813
|
+
}
|
|
4143
4814
|
// this.cameraControls.this.camera.far = maxBorder * 10; // 设置相机的远裁剪面
|
|
4144
4815
|
this.cameraControls.minDistance = maxBorder * 0.2; // 动态设置视角滚轮的距离
|
|
4816
|
+
// this.cameraControls.minDistance = 1000; // 动态设置视角滚轮的距离
|
|
4145
4817
|
this.camera.updateProjectionMatrix();
|
|
4146
4818
|
},
|
|
4147
4819
|
// 获取mesh的中心点
|
|
@@ -4469,8 +5141,19 @@ export default {
|
|
|
4469
5141
|
if (this.threeMeasure) {
|
|
4470
5142
|
this.threeMeasure.updateParams(width, height);
|
|
4471
5143
|
}
|
|
5144
|
+
this.updateMeshLineResolution(width, height);
|
|
4472
5145
|
}
|
|
4473
5146
|
},
|
|
5147
|
+
updateMeshLineResolution(width, height) {
|
|
5148
|
+
if (!this.modelGroup || !width || !height) return;
|
|
5149
|
+
this.modelGroup.traverse(child => {
|
|
5150
|
+
const material = child && child.material;
|
|
5151
|
+
if (material && material.type === 'MeshLineMaterial' && material.resolution) {
|
|
5152
|
+
material.resolution.set(width, height);
|
|
5153
|
+
material.needsUpdate = true;
|
|
5154
|
+
}
|
|
5155
|
+
});
|
|
5156
|
+
},
|
|
4474
5157
|
setModelCenter(target, options = {}) {
|
|
4475
5158
|
// return
|
|
4476
5159
|
let box3;
|
|
@@ -4644,6 +5327,7 @@ export default {
|
|
|
4644
5327
|
children.instanceMatrix.needsUpdate = true;
|
|
4645
5328
|
});
|
|
4646
5329
|
this.refreshCollisionObbDebug();
|
|
5330
|
+
this.refreshOcclusionBoundsDebug();
|
|
4647
5331
|
this.notifyCameraChange(); // 触发场景更新
|
|
4648
5332
|
break;
|
|
4649
5333
|
}
|
|
@@ -4749,6 +5433,7 @@ export default {
|
|
|
4749
5433
|
this.buildOctreeFromBoxIndex();
|
|
4750
5434
|
}
|
|
4751
5435
|
this.refreshCollisionObbDebug();
|
|
5436
|
+
this.refreshOcclusionBoundsDebug();
|
|
4752
5437
|
},
|
|
4753
5438
|
showOutlinePass(instanceId) {
|
|
4754
5439
|
let targetObj = this.getObjectByName(instanceId);
|
|
@@ -4896,6 +5581,7 @@ export default {
|
|
|
4896
5581
|
children.instanceMatrix.needsUpdate = true;
|
|
4897
5582
|
});
|
|
4898
5583
|
this.refreshCollisionObbDebug();
|
|
5584
|
+
this.refreshOcclusionBoundsDebug();
|
|
4899
5585
|
break;
|
|
4900
5586
|
}
|
|
4901
5587
|
case 'opacity':
|
|
@@ -5317,6 +6003,7 @@ export default {
|
|
|
5317
6003
|
}
|
|
5318
6004
|
this.clearBypassCullingModelIds();
|
|
5319
6005
|
this.hideCollisionObbDebug();
|
|
6006
|
+
this.hideOcclusionBoundsDebug();
|
|
5320
6007
|
|
|
5321
6008
|
if (this.scene) {
|
|
5322
6009
|
this.removeTraverse();
|
|
@@ -5582,6 +6269,7 @@ export default {
|
|
|
5582
6269
|
if (nodeType === 'custom-root') return true;
|
|
5583
6270
|
if (userData.transformControlHelper === true) return true;
|
|
5584
6271
|
if (userData.collisionObbDebugHelper === true) return true;
|
|
6272
|
+
if (userData.occlusionBoundsDebugHelper === true) return true;
|
|
5585
6273
|
if (object.isCamera || object.isLight) return true;
|
|
5586
6274
|
if (typeof object.type === 'string' && /Helper$/i.test(object.type)) return true;
|
|
5587
6275
|
return object.type === 'CSS2DObject' || object.type === 'TransformControlsRoot';
|
|
@@ -6545,6 +7233,7 @@ export default {
|
|
|
6545
7233
|
});
|
|
6546
7234
|
}
|
|
6547
7235
|
this.cameraControls.enabled && this.cameraControls.update(this.timeStamp);
|
|
7236
|
+
this.observeCameraControlMove();
|
|
6548
7237
|
// 计算相机近裁面到包围盒当前面的距离
|
|
6549
7238
|
this.cameraTrack();
|
|
6550
7239
|
this.firstPerspective();
|
|
@@ -6692,6 +7381,7 @@ export default {
|
|
|
6692
7381
|
object.userData.instancesMap instanceof Map &&
|
|
6693
7382
|
object.userData.instancesMap.get(instanceId);
|
|
6694
7383
|
if (instanceInfo && instanceInfo.visible === false) return false;
|
|
7384
|
+
if (instanceInfo && instanceInfo.cullingVisible === false) return false;
|
|
6695
7385
|
|
|
6696
7386
|
const box =
|
|
6697
7387
|
this._boxIndex && this._boxIndex.has(String(instanceId))
|
|
@@ -6755,6 +7445,13 @@ export default {
|
|
|
6755
7445
|
const loadingState = this.noObserver
|
|
6756
7446
|
? this.noObserver.batchLoadingState
|
|
6757
7447
|
: this.batchLoadingState;
|
|
7448
|
+
const rect = this.renderer
|
|
7449
|
+
? this.renderer.domElement.getBoundingClientRect()
|
|
7450
|
+
: { width: window.innerWidth, height: window.innerHeight };
|
|
7451
|
+
options.meshLineResolution = {
|
|
7452
|
+
width: rect.width || window.innerWidth || 1,
|
|
7453
|
+
height: rect.height || window.innerHeight || 1,
|
|
7454
|
+
};
|
|
6758
7455
|
// 如果已经在加载中,先停止之前的加载
|
|
6759
7456
|
if (loadingState.isLoading) {
|
|
6760
7457
|
this.stopBatchLoading('restart');
|
|
@@ -7318,8 +8015,15 @@ export default {
|
|
|
7318
8015
|
|
|
7319
8016
|
loadingState.interactionState.wheelTimeout = setTimeout(() => {
|
|
7320
8017
|
this.$emit('wheelEnd', event);
|
|
7321
|
-
this.endInteraction('wheel', event, { immediateResume: false });
|
|
7322
8018
|
loadingState.interactionState.wheelTimeout = null;
|
|
8019
|
+
if (this.isCameraControlMoveObserverEnabled()) {
|
|
8020
|
+
this.resetCameraControlMoveSnapshot(false);
|
|
8021
|
+
if (typeof this._cameraChangeObserver === 'function') {
|
|
8022
|
+
this._cameraChangeObserver('wheel');
|
|
8023
|
+
}
|
|
8024
|
+
} else {
|
|
8025
|
+
this.endInteraction('wheel', event, { immediateResume: false });
|
|
8026
|
+
}
|
|
7323
8027
|
}, delay);
|
|
7324
8028
|
},
|
|
7325
8029
|
|
|
@@ -7347,9 +8051,87 @@ export default {
|
|
|
7347
8051
|
/**
|
|
7348
8052
|
* 隐藏场景包围盒
|
|
7349
8053
|
*/
|
|
8054
|
+
refreshSceneBoundingBoxHelper(forceVisible = this.boundingBoxVisible) {
|
|
8055
|
+
if (this.sceneBoundingBoxHelper && this.scene) {
|
|
8056
|
+
this.scene.remove(this.sceneBoundingBoxHelper);
|
|
8057
|
+
this.sceneBoundingBoxHelper.geometry && this.sceneBoundingBoxHelper.geometry.dispose();
|
|
8058
|
+
if (Array.isArray(this.sceneBoundingBoxHelper.material)) {
|
|
8059
|
+
this.sceneBoundingBoxHelper.material.forEach(material => {
|
|
8060
|
+
material && material.dispose && material.dispose();
|
|
8061
|
+
});
|
|
8062
|
+
} else if (
|
|
8063
|
+
this.sceneBoundingBoxHelper.material &&
|
|
8064
|
+
this.sceneBoundingBoxHelper.material.dispose
|
|
8065
|
+
) {
|
|
8066
|
+
this.sceneBoundingBoxHelper.material.dispose();
|
|
8067
|
+
}
|
|
8068
|
+
this.sceneBoundingBoxHelper = null;
|
|
8069
|
+
}
|
|
8070
|
+
|
|
8071
|
+
if (
|
|
8072
|
+
!forceVisible ||
|
|
8073
|
+
!this.scene ||
|
|
8074
|
+
!this.sceneBoundingBox ||
|
|
8075
|
+
!this.sceneBoundingBox.isBox3 ||
|
|
8076
|
+
this.sceneBoundingBox.isEmpty()
|
|
8077
|
+
) {
|
|
8078
|
+
this.boundingBoxVisible = false;
|
|
8079
|
+
return false;
|
|
8080
|
+
}
|
|
8081
|
+
|
|
8082
|
+
const helper = new this.THREE.Box3Helper(this.sceneBoundingBox, 0x00ff88);
|
|
8083
|
+
helper.name = 'sceneBoundingBoxHelper';
|
|
8084
|
+
helper.renderOrder = 999;
|
|
8085
|
+
if (helper.material) {
|
|
8086
|
+
helper.material.depthTest = false;
|
|
8087
|
+
helper.material.transparent = true;
|
|
8088
|
+
helper.material.opacity = 0.9;
|
|
8089
|
+
}
|
|
8090
|
+
this.scene.add(helper);
|
|
8091
|
+
this.sceneBoundingBoxHelper = helper;
|
|
8092
|
+
this.boundingBoxVisible = true;
|
|
8093
|
+
return true;
|
|
8094
|
+
},
|
|
8095
|
+
|
|
8096
|
+
showSceneBoundingBox() {
|
|
8097
|
+
this.sceneBoundingBoxDebugEnabled = true;
|
|
8098
|
+
const shown = this.refreshSceneBoundingBoxHelper(true);
|
|
8099
|
+
if (!shown) {
|
|
8100
|
+
console.warn('sceneBoundingBox 无有效范围,无法绘制调试线框');
|
|
8101
|
+
return;
|
|
8102
|
+
}
|
|
8103
|
+
|
|
8104
|
+
const size = this.sceneBoundingBox.getSize(new this.THREE.Vector3());
|
|
8105
|
+
const center = this.sceneBoundingBox.getCenter(new this.THREE.Vector3());
|
|
8106
|
+
console.log('sceneBoundingBox 线框已绘制', {
|
|
8107
|
+
size: {
|
|
8108
|
+
x: size.x,
|
|
8109
|
+
y: size.y,
|
|
8110
|
+
z: size.z,
|
|
8111
|
+
},
|
|
8112
|
+
center: {
|
|
8113
|
+
x: center.x,
|
|
8114
|
+
y: center.y,
|
|
8115
|
+
z: center.z,
|
|
8116
|
+
},
|
|
8117
|
+
});
|
|
8118
|
+
},
|
|
8119
|
+
|
|
7350
8120
|
hideSceneBoundingBox() {
|
|
8121
|
+
this.sceneBoundingBoxDebugEnabled = false;
|
|
7351
8122
|
if (this.sceneBoundingBoxHelper && this.scene) {
|
|
7352
8123
|
this.scene.remove(this.sceneBoundingBoxHelper);
|
|
8124
|
+
this.sceneBoundingBoxHelper.geometry && this.sceneBoundingBoxHelper.geometry.dispose();
|
|
8125
|
+
if (Array.isArray(this.sceneBoundingBoxHelper.material)) {
|
|
8126
|
+
this.sceneBoundingBoxHelper.material.forEach(material => {
|
|
8127
|
+
material && material.dispose && material.dispose();
|
|
8128
|
+
});
|
|
8129
|
+
} else if (
|
|
8130
|
+
this.sceneBoundingBoxHelper.material &&
|
|
8131
|
+
this.sceneBoundingBoxHelper.material.dispose
|
|
8132
|
+
) {
|
|
8133
|
+
this.sceneBoundingBoxHelper.material.dispose();
|
|
8134
|
+
}
|
|
7353
8135
|
this.sceneBoundingBoxHelper = null;
|
|
7354
8136
|
this.boundingBoxVisible = false;
|
|
7355
8137
|
console.log('场景包围盒已隐藏');
|
|
@@ -7360,7 +8142,7 @@ export default {
|
|
|
7360
8142
|
* 切换场景包围盒显示状态
|
|
7361
8143
|
*/
|
|
7362
8144
|
toggleSceneBoundingBox() {
|
|
7363
|
-
if (this.
|
|
8145
|
+
if (this.sceneBoundingBoxDebugEnabled) {
|
|
7364
8146
|
this.hideSceneBoundingBox();
|
|
7365
8147
|
} else {
|
|
7366
8148
|
this.showSceneBoundingBox();
|