fl-web-component 2.2.2 → 2.2.4
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.
|
@@ -22,11 +22,91 @@
|
|
|
22
22
|
<div class="loading-text">{{ modelLoadingState.text }}</div>
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
25
|
+
<el-dialog
|
|
26
|
+
class="roaming-settings-dialog"
|
|
27
|
+
title="漫游设置"
|
|
28
|
+
width="520px"
|
|
29
|
+
:visible.sync="roamingSettingsVisible"
|
|
30
|
+
:append-to-body="true"
|
|
31
|
+
:close-on-click-modal="false"
|
|
32
|
+
@close="handleRoamingSettingsDialogClose"
|
|
33
|
+
>
|
|
34
|
+
<el-form
|
|
35
|
+
v-if="roamingSettingsDraft"
|
|
36
|
+
ref="roamingSettingsForm"
|
|
37
|
+
:model="roamingSettingsDraft"
|
|
38
|
+
label-width="100px"
|
|
39
|
+
size="small"
|
|
40
|
+
class="roaming-settings-form"
|
|
41
|
+
>
|
|
42
|
+
<div class="roaming-settings-section-title">视角</div>
|
|
43
|
+
<el-form-item label="视角">
|
|
44
|
+
<el-radio-group
|
|
45
|
+
v-model="roamingSettingsDraft.perspective"
|
|
46
|
+
class="roaming-settings-perspective"
|
|
47
|
+
>
|
|
48
|
+
<el-radio label="first-person">第一人称</el-radio>
|
|
49
|
+
<el-radio label="third-person">第三人称</el-radio>
|
|
50
|
+
</el-radio-group>
|
|
51
|
+
</el-form-item>
|
|
52
|
+
|
|
53
|
+
<div class="roaming-settings-section-title">人物属性</div>
|
|
54
|
+
<el-form-item label="人物身高">
|
|
55
|
+
<el-input
|
|
56
|
+
v-model.number="roamingSettingsDraft.height"
|
|
57
|
+
type="number"
|
|
58
|
+
:min="roamingSettingsLimits.height.min"
|
|
59
|
+
:max="roamingSettingsLimits.height.max"
|
|
60
|
+
:step="1"
|
|
61
|
+
class="roaming-settings-height"
|
|
62
|
+
>
|
|
63
|
+
<span slot="append">cm</span>
|
|
64
|
+
</el-input>
|
|
65
|
+
</el-form-item>
|
|
66
|
+
<el-form-item label="设置障碍">
|
|
67
|
+
<el-select
|
|
68
|
+
v-model="roamingSettingsDraft.collisionEnabled"
|
|
69
|
+
class="roaming-settings-control"
|
|
70
|
+
>
|
|
71
|
+
<el-option label="开启" :value="true"></el-option>
|
|
72
|
+
<el-option label="关闭" :value="false"></el-option>
|
|
73
|
+
</el-select>
|
|
74
|
+
</el-form-item>
|
|
75
|
+
<el-form-item label="移动速度">
|
|
76
|
+
<el-slider
|
|
77
|
+
v-model="roamingSettingsDraft.moveSpeed"
|
|
78
|
+
:min="roamingSettingsLimits.moveSpeed.min"
|
|
79
|
+
:max="roamingSettingsLimits.moveSpeed.max"
|
|
80
|
+
:step="roamingSettingsLimits.moveSpeed.step"
|
|
81
|
+
></el-slider>
|
|
82
|
+
</el-form-item>
|
|
83
|
+
<el-form-item label="鼠标速度">
|
|
84
|
+
<el-slider
|
|
85
|
+
v-model="roamingSettingsDraft.mouseSpeed"
|
|
86
|
+
:min="roamingSettingsLimits.mouseSpeed.min"
|
|
87
|
+
:max="roamingSettingsLimits.mouseSpeed.max"
|
|
88
|
+
:step="roamingSettingsLimits.mouseSpeed.step"
|
|
89
|
+
></el-slider>
|
|
90
|
+
</el-form-item>
|
|
91
|
+
</el-form>
|
|
92
|
+
<div slot="footer" class="roaming-settings-footer">
|
|
93
|
+
<el-button size="small" @click="cancelRoamingSettings">取消</el-button>
|
|
94
|
+
<el-button size="small" type="primary" @click="confirmRoamingSettings">确定</el-button>
|
|
95
|
+
</div>
|
|
96
|
+
</el-dialog>
|
|
25
97
|
</div>
|
|
26
98
|
</template>
|
|
27
99
|
<script>
|
|
28
100
|
var singleFrameTime = 1 / 30;
|
|
29
101
|
var dragThreshold = 5; // 拖拽阈值,像素单位
|
|
102
|
+
const ORIENTATION_AXIS_VIEWPORT_SIZE = 120;
|
|
103
|
+
const ORIENTATION_AXIS_VIEWPORT_MARGIN = 16;
|
|
104
|
+
const ORIENTATION_AXIS_LENGTH = 0.68;
|
|
105
|
+
const ORIENTATION_AXIS_HEAD_LENGTH = 0.21;
|
|
106
|
+
const ORIENTATION_AXIS_HEAD_RADIUS = 0.12;
|
|
107
|
+
const ORIENTATION_AXIS_SHAFT_RADIUS = 0.045;
|
|
108
|
+
const ORIENTATION_AXIS_LABEL_OFFSET = 0.12;
|
|
109
|
+
const ORIENTATION_AXIS_LABEL_SIZE = 0.3;
|
|
30
110
|
|
|
31
111
|
// camera-controls 相机限位参数,见 setCameraConfig / applyCameraBoundary
|
|
32
112
|
// minDistance 是相机到目标点的最小距离,取场景最大边长的千分之一,并夹在上下限之间。
|
|
@@ -38,7 +118,7 @@ var orbitMinDistanceCeiling = 50;
|
|
|
38
118
|
// 相机离目标点的最远距离,按场景包围盒对角线倍数给
|
|
39
119
|
var orbitMaxDistanceRatio = 3;
|
|
40
120
|
// 目标点可活动范围相对场景包围盒的外扩倍数(1 表示不超出包围盒)
|
|
41
|
-
var orbitBoundaryRatio =
|
|
121
|
+
var orbitBoundaryRatio = 3;
|
|
42
122
|
// 左键切换旋转中心时,命中点与当前目标点的最小位移(占场景最大边长比例),小于该值视为重复点击
|
|
43
123
|
var orbitPointMoveRatio = 1e-4;
|
|
44
124
|
|
|
@@ -154,6 +234,14 @@ var clippingMesh = [],
|
|
|
154
234
|
|
|
155
235
|
const FIRST_PERSON_GROUND_HEIGHT = 1680;
|
|
156
236
|
const FIRST_PERSON_POINTER_SPEED = 0.3;
|
|
237
|
+
const THIRD_PERSON_MODEL_NAME = '__third_person_roaming_avatar__';
|
|
238
|
+
const THIRD_PERSON_CAMERA_DISTANCE = 12500;
|
|
239
|
+
const THIRD_PERSON_CAMERA_MIN_DISTANCE_RATIO = 1;
|
|
240
|
+
const THIRD_PERSON_CAMERA_MAX_DISTANCE_MULTIPLIER = 10;
|
|
241
|
+
const THIRD_PERSON_CAMERA_HEIGHT_RATIO = 0.65;
|
|
242
|
+
const THIRD_PERSON_COLLISION_RADIUS_RATIO = 0.16;
|
|
243
|
+
const THIRD_PERSON_GRAVITY = 9800;
|
|
244
|
+
const THIRD_PERSON_GROUND_SNAP_DISTANCE = 12;
|
|
157
245
|
|
|
158
246
|
var removeSpeed = 1000,
|
|
159
247
|
upSpeed = 200; //控制器移动速度 , //控制跳起时的速度
|
|
@@ -162,6 +250,40 @@ var roamConfig = {
|
|
|
162
250
|
speed: 0, // 最大值为3
|
|
163
251
|
name: '',
|
|
164
252
|
};
|
|
253
|
+
const ROAMING_SETTINGS_DEFAULTS = Object.freeze({
|
|
254
|
+
perspective: 'first-person',
|
|
255
|
+
height: 168,
|
|
256
|
+
collisionEnabled: true,
|
|
257
|
+
moveSpeed: 1,
|
|
258
|
+
mouseSpeed: 1,
|
|
259
|
+
});
|
|
260
|
+
const ROAMING_SETTINGS_LIMITS = Object.freeze({
|
|
261
|
+
height: { min: 0.1, max: 250 },
|
|
262
|
+
moveSpeed: { min: 0.01, max: 5, step: 0.01 },
|
|
263
|
+
mouseSpeed: { min: 0.01, max: 5, step: 0.01 },
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
function createRoamingSettings(config) {
|
|
267
|
+
const source = config && typeof config === 'object' ? config : {};
|
|
268
|
+
const settings = { ...ROAMING_SETTINGS_DEFAULTS };
|
|
269
|
+
if (source.perspective === 'first-person' || source.perspective === 'third-person') {
|
|
270
|
+
settings.perspective = source.perspective;
|
|
271
|
+
}
|
|
272
|
+
if (typeof source.collisionEnabled === 'boolean') {
|
|
273
|
+
settings.collisionEnabled = source.collisionEnabled;
|
|
274
|
+
}
|
|
275
|
+
['height', 'moveSpeed', 'mouseSpeed'].forEach(key => {
|
|
276
|
+
const value = Number(source[key]);
|
|
277
|
+
if (Number.isFinite(value)) {
|
|
278
|
+
settings[key] = value;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
return settings;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function copyRoamingSettings(config) {
|
|
285
|
+
return { ...createRoamingSettings(config) };
|
|
286
|
+
}
|
|
165
287
|
var guiParams = {
|
|
166
288
|
x轴: 0,
|
|
167
289
|
y轴: 0,
|
|
@@ -281,6 +403,8 @@ import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js';
|
|
|
281
403
|
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js';
|
|
282
404
|
import Stats from 'three/examples/jsm/libs/stats.module.js';
|
|
283
405
|
import { OBB } from 'three/examples/jsm/math/OBB.js';
|
|
406
|
+
import { MeshLineGeometry, MeshLineMaterial } from 'meshline';
|
|
407
|
+
import workerModelUrl from './assets/worker.glb';
|
|
284
408
|
// import boxJson from './box.json';
|
|
285
409
|
import {
|
|
286
410
|
StreamLoader,
|
|
@@ -293,8 +417,8 @@ import { EDITOR_EVENT, SCENE_NODE_TYPE, TRANSFORM_MODE } from '@/utils/threejs/e
|
|
|
293
417
|
import { isCustomRoot, isTransformAttachableObject } from '@/utils/threejs/editor/scene-helpers.js';
|
|
294
418
|
import { onContextHandle } from './component/context';
|
|
295
419
|
|
|
296
|
-
|
|
297
|
-
//
|
|
420
|
+
let isDebug = process.env.NODE_ENV !== 'production' || process.env.VUE_APP_IS_WATCH === true;
|
|
421
|
+
// isDebug = false;
|
|
298
422
|
const COLLISION_CONTACT_EPSILON = 0.1;
|
|
299
423
|
const COLLISION_OBB_AXIS_EPSILON = 1e-8;
|
|
300
424
|
const COLLISION_CONNECTION_DISTANCE_TOLERANCE = 1;
|
|
@@ -380,6 +504,11 @@ export default {
|
|
|
380
504
|
visible: false,
|
|
381
505
|
text: '加载中',
|
|
382
506
|
},
|
|
507
|
+
roamingSettingsVisible: false,
|
|
508
|
+
roamingSettingsDraft: null,
|
|
509
|
+
roamingSettings: createRoamingSettings(),
|
|
510
|
+
roamingSettingsCloseReason: '',
|
|
511
|
+
roamingSettingsLimits: ROAMING_SETTINGS_LIMITS,
|
|
383
512
|
isolateMode: false,
|
|
384
513
|
// collisionObbDebugEnabled: isDebug,
|
|
385
514
|
sceneBoundingBoxDebugEnabled: isDebug,
|
|
@@ -428,6 +557,9 @@ export default {
|
|
|
428
557
|
'velocity',
|
|
429
558
|
'direction',
|
|
430
559
|
'clock',
|
|
560
|
+
'firstPersonMouseSpeed',
|
|
561
|
+
'firstPersonCollisionEnabled',
|
|
562
|
+
'firstPersonActorHeight',
|
|
431
563
|
'pointControls',
|
|
432
564
|
'threeMeasure',
|
|
433
565
|
'modelGroup',
|
|
@@ -443,7 +575,14 @@ export default {
|
|
|
443
575
|
'centeringDebounceTimer',
|
|
444
576
|
'sceneBoundingBox',
|
|
445
577
|
'sceneBoundingBoxHelper',
|
|
578
|
+
'globalClippingBoundsHelper',
|
|
446
579
|
'collisionObbDebugGroup',
|
|
580
|
+
'_thirdPersonState',
|
|
581
|
+
'_thirdPersonMixer',
|
|
582
|
+
'_thirdPersonPointerLockHandler',
|
|
583
|
+
'_thirdPersonMouseMoveHandler',
|
|
584
|
+
'_thirdPersonWheelHandler',
|
|
585
|
+
'_thirdPersonPointerErrorHandler',
|
|
447
586
|
'occlusionBoundsDebugGroup',
|
|
448
587
|
];
|
|
449
588
|
arr.forEach(item => {
|
|
@@ -653,6 +792,9 @@ export default {
|
|
|
653
792
|
this.initSceneCommandService();
|
|
654
793
|
this.initCamera();
|
|
655
794
|
this.initControl();
|
|
795
|
+
if (this._orientationAxisEnabled !== false) {
|
|
796
|
+
this.initOrientationAxis();
|
|
797
|
+
}
|
|
656
798
|
this.initTransformControls();
|
|
657
799
|
this.initPostProcessing();
|
|
658
800
|
// 初始化统一的相机事件监听
|
|
@@ -673,6 +815,187 @@ export default {
|
|
|
673
815
|
this.destroyScene();
|
|
674
816
|
},
|
|
675
817
|
methods: {
|
|
818
|
+
setOrientationAxisEnabled(enabled = true) {
|
|
819
|
+
this._orientationAxisEnabled = enabled !== false;
|
|
820
|
+
if (!this._orientationAxisEnabled) {
|
|
821
|
+
this.disposeOrientationAxis();
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
if (!this._orientationAxisState && this.THREE) {
|
|
826
|
+
this.initOrientationAxis();
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
initOrientationAxis() {
|
|
830
|
+
const THREE = this.THREE;
|
|
831
|
+
const state = {
|
|
832
|
+
scene: new THREE.Scene(),
|
|
833
|
+
camera: new THREE.OrthographicCamera(-1, 1, 1, -1, 0.1, 10),
|
|
834
|
+
group: new THREE.Group(),
|
|
835
|
+
inverseCameraQuaternion: new THREE.Quaternion(),
|
|
836
|
+
renderSize: new THREE.Vector2(),
|
|
837
|
+
previousViewport: new THREE.Vector4(),
|
|
838
|
+
previousScissor: new THREE.Vector4(),
|
|
839
|
+
resources: {
|
|
840
|
+
geometries: [],
|
|
841
|
+
materials: [],
|
|
842
|
+
textures: [],
|
|
843
|
+
},
|
|
844
|
+
};
|
|
845
|
+
this._orientationAxisState = state;
|
|
846
|
+
|
|
847
|
+
state.camera.position.set(0, 0, 3);
|
|
848
|
+
state.camera.lookAt(0, 0, 0);
|
|
849
|
+
|
|
850
|
+
const axes = [
|
|
851
|
+
{ label: 'X', color: 0xff453a, direction: new THREE.Vector3(1, 0, 0) },
|
|
852
|
+
{ label: 'Y', color: 0x30d158, direction: new THREE.Vector3(0, 1, 0) },
|
|
853
|
+
{ label: 'Z', color: 0x0a84ff, direction: new THREE.Vector3(0, 0, 1) },
|
|
854
|
+
];
|
|
855
|
+
const up = new THREE.Vector3(0, 1, 0);
|
|
856
|
+
const shaftLength = ORIENTATION_AXIS_LENGTH - ORIENTATION_AXIS_HEAD_LENGTH;
|
|
857
|
+
|
|
858
|
+
axes.forEach(axis => {
|
|
859
|
+
const material = new THREE.MeshBasicMaterial({
|
|
860
|
+
color: axis.color,
|
|
861
|
+
depthTest: true,
|
|
862
|
+
depthWrite: true,
|
|
863
|
+
toneMapped: false,
|
|
864
|
+
});
|
|
865
|
+
const shaftGeometry = new THREE.CylinderGeometry(
|
|
866
|
+
ORIENTATION_AXIS_SHAFT_RADIUS,
|
|
867
|
+
ORIENTATION_AXIS_SHAFT_RADIUS,
|
|
868
|
+
shaftLength,
|
|
869
|
+
8
|
|
870
|
+
);
|
|
871
|
+
const headGeometry = new THREE.ConeGeometry(
|
|
872
|
+
ORIENTATION_AXIS_HEAD_RADIUS,
|
|
873
|
+
ORIENTATION_AXIS_HEAD_LENGTH,
|
|
874
|
+
10
|
|
875
|
+
);
|
|
876
|
+
const orientation = new THREE.Quaternion().setFromUnitVectors(up, axis.direction);
|
|
877
|
+
const shaft = new THREE.Mesh(shaftGeometry, material);
|
|
878
|
+
const head = new THREE.Mesh(headGeometry, material);
|
|
879
|
+
|
|
880
|
+
shaft.position.copy(axis.direction).multiplyScalar(shaftLength / 2);
|
|
881
|
+
head.position
|
|
882
|
+
.copy(axis.direction)
|
|
883
|
+
.multiplyScalar(ORIENTATION_AXIS_LENGTH - ORIENTATION_AXIS_HEAD_LENGTH / 2);
|
|
884
|
+
shaft.quaternion.copy(orientation);
|
|
885
|
+
head.quaternion.copy(orientation);
|
|
886
|
+
state.group.add(shaft, head);
|
|
887
|
+
|
|
888
|
+
state.resources.geometries.push(shaftGeometry, headGeometry);
|
|
889
|
+
state.resources.materials.push(material);
|
|
890
|
+
|
|
891
|
+
const texture = this.createOrientationAxisLabelTexture(axis.label);
|
|
892
|
+
if (texture) {
|
|
893
|
+
const labelMaterial = new THREE.SpriteMaterial({
|
|
894
|
+
map: texture,
|
|
895
|
+
transparent: true,
|
|
896
|
+
depthTest: true,
|
|
897
|
+
depthWrite: false,
|
|
898
|
+
toneMapped: false,
|
|
899
|
+
});
|
|
900
|
+
const label = new THREE.Sprite(labelMaterial);
|
|
901
|
+
label.position
|
|
902
|
+
.copy(axis.direction)
|
|
903
|
+
.multiplyScalar(ORIENTATION_AXIS_LENGTH + ORIENTATION_AXIS_LABEL_OFFSET);
|
|
904
|
+
label.scale.set(ORIENTATION_AXIS_LABEL_SIZE, ORIENTATION_AXIS_LABEL_SIZE, 1);
|
|
905
|
+
label.renderOrder = 2;
|
|
906
|
+
state.group.add(label);
|
|
907
|
+
state.resources.materials.push(labelMaterial);
|
|
908
|
+
state.resources.textures.push(texture);
|
|
909
|
+
}
|
|
910
|
+
});
|
|
911
|
+
|
|
912
|
+
state.scene.add(state.group);
|
|
913
|
+
},
|
|
914
|
+
createOrientationAxisLabelTexture(text) {
|
|
915
|
+
const canvas = document.createElement('canvas');
|
|
916
|
+
canvas.width = 128;
|
|
917
|
+
canvas.height = 128;
|
|
918
|
+
const context = canvas.getContext('2d');
|
|
919
|
+
if (!context) return null;
|
|
920
|
+
|
|
921
|
+
context.font = '700 88px Arial, sans-serif';
|
|
922
|
+
context.textAlign = 'center';
|
|
923
|
+
context.textBaseline = 'middle';
|
|
924
|
+
context.lineJoin = 'round';
|
|
925
|
+
context.lineWidth = 9;
|
|
926
|
+
context.strokeStyle = 'rgba(255, 255, 255, 0.9)';
|
|
927
|
+
context.strokeText(text, 64, 68);
|
|
928
|
+
context.fillStyle = '#17202a';
|
|
929
|
+
context.fillText(text, 64, 68);
|
|
930
|
+
|
|
931
|
+
const texture = new this.THREE.CanvasTexture(canvas);
|
|
932
|
+
texture.colorSpace = this.THREE.SRGBColorSpace;
|
|
933
|
+
texture.generateMipmaps = false;
|
|
934
|
+
texture.minFilter = this.THREE.LinearFilter;
|
|
935
|
+
texture.magFilter = this.THREE.LinearFilter;
|
|
936
|
+
texture.needsUpdate = true;
|
|
937
|
+
return texture;
|
|
938
|
+
},
|
|
939
|
+
syncOrientationAxis() {
|
|
940
|
+
const state = this._orientationAxisState;
|
|
941
|
+
if (!state || !this.camera) return;
|
|
942
|
+
|
|
943
|
+
this.camera.getWorldQuaternion(state.inverseCameraQuaternion);
|
|
944
|
+
state.group.quaternion.copy(state.inverseCameraQuaternion).invert();
|
|
945
|
+
},
|
|
946
|
+
renderOrientationAxis() {
|
|
947
|
+
const renderer = this.renderer;
|
|
948
|
+
const state = this._orientationAxisState;
|
|
949
|
+
if (this._orientationAxisEnabled === false || !renderer || !state) return;
|
|
950
|
+
|
|
951
|
+
renderer.getSize(state.renderSize);
|
|
952
|
+
const viewportSize = Math.min(
|
|
953
|
+
ORIENTATION_AXIS_VIEWPORT_SIZE,
|
|
954
|
+
state.renderSize.x,
|
|
955
|
+
state.renderSize.y
|
|
956
|
+
);
|
|
957
|
+
if (!viewportSize) return;
|
|
958
|
+
|
|
959
|
+
const x = Math.max(
|
|
960
|
+
0,
|
|
961
|
+
Math.min(ORIENTATION_AXIS_VIEWPORT_MARGIN, state.renderSize.x - viewportSize)
|
|
962
|
+
);
|
|
963
|
+
const y = Math.max(
|
|
964
|
+
0,
|
|
965
|
+
Math.min(ORIENTATION_AXIS_VIEWPORT_MARGIN, state.renderSize.y - viewportSize)
|
|
966
|
+
);
|
|
967
|
+
const previousRenderTarget = renderer.getRenderTarget();
|
|
968
|
+
const previousScissorTest = renderer.getScissorTest();
|
|
969
|
+
const previousAutoClear = renderer.autoClear;
|
|
970
|
+
renderer.getViewport(state.previousViewport);
|
|
971
|
+
renderer.getScissor(state.previousScissor);
|
|
972
|
+
|
|
973
|
+
try {
|
|
974
|
+
renderer.setRenderTarget(null);
|
|
975
|
+
renderer.setViewport(x, y, viewportSize, viewportSize);
|
|
976
|
+
renderer.setScissor(x, y, viewportSize, viewportSize);
|
|
977
|
+
renderer.setScissorTest(true);
|
|
978
|
+
renderer.autoClear = false;
|
|
979
|
+
renderer.clearDepth();
|
|
980
|
+
renderer.render(state.scene, state.camera);
|
|
981
|
+
} finally {
|
|
982
|
+
renderer.autoClear = previousAutoClear;
|
|
983
|
+
renderer.setRenderTarget(previousRenderTarget);
|
|
984
|
+
renderer.setViewport(state.previousViewport);
|
|
985
|
+
renderer.setScissor(state.previousScissor);
|
|
986
|
+
renderer.setScissorTest(previousScissorTest);
|
|
987
|
+
}
|
|
988
|
+
},
|
|
989
|
+
disposeOrientationAxis() {
|
|
990
|
+
const state = this._orientationAxisState;
|
|
991
|
+
if (!state) return;
|
|
992
|
+
|
|
993
|
+
state.resources.geometries.forEach(geometry => geometry.dispose());
|
|
994
|
+
state.resources.materials.forEach(material => material.dispose());
|
|
995
|
+
state.resources.textures.forEach(texture => texture.dispose());
|
|
996
|
+
state.scene.clear();
|
|
997
|
+
this._orientationAxisState = null;
|
|
998
|
+
},
|
|
676
999
|
bindScenePointerEvents() {
|
|
677
1000
|
if (!this.renderer || !this.renderer.domElement) return;
|
|
678
1001
|
this.renderer.domElement.addEventListener('pointerup', this.mouseClick, true);
|
|
@@ -3725,6 +4048,8 @@ export default {
|
|
|
3725
4048
|
}
|
|
3726
4049
|
}
|
|
3727
4050
|
cam.far = newFar;
|
|
4051
|
+
// cam.far = 200;
|
|
4052
|
+
// cam.near = 0.1;
|
|
3728
4053
|
cam.updateProjectionMatrix();
|
|
3729
4054
|
},
|
|
3730
4055
|
/**
|
|
@@ -5301,14 +5626,17 @@ export default {
|
|
|
5301
5626
|
* @param {Function} [config.boxLoadingText] - 自定义进度文案函数
|
|
5302
5627
|
* @param {Function} [config.onBoxProgress] - 包围盒下载进度回调
|
|
5303
5628
|
* @param {boolean} [config.showModelLoading=true] - 是否显示模型加载(裁剪 → 请求 → 分批上屏)进度提示
|
|
5629
|
+
* @param {boolean} [config.showOrientationAxis=true] - 是否显示左下角 XYZ 坐标轴
|
|
5304
5630
|
* @param {string} [config.modelLoadingText="加载中"] - 自定义模型加载进度文案
|
|
5305
5631
|
* @param {Function} [config.onStreamProgress] - 几何流式加载阶段回调(透传 StreamLoader 事件)
|
|
5306
5632
|
*/
|
|
5307
5633
|
init(config = {}) {
|
|
5308
5634
|
console.log('init');
|
|
5635
|
+
const { showOrientationAxis, ...streamLoaderConfig } = config;
|
|
5636
|
+
this.setOrientationAxisEnabled(showOrientationAxis !== false);
|
|
5309
5637
|
this.setCameraControlMoveObserverConfig(config);
|
|
5310
5638
|
this.setProgressiveRenderingConfig(config);
|
|
5311
|
-
this.initStreamLoader(config.modelApi,
|
|
5639
|
+
this.initStreamLoader(config.modelApi, streamLoaderConfig);
|
|
5312
5640
|
},
|
|
5313
5641
|
|
|
5314
5642
|
/**
|
|
@@ -6920,7 +7248,7 @@ export default {
|
|
|
6920
7248
|
source => {
|
|
6921
7249
|
// 漫游、第一人称移动、普通视角拖拽/缩放中,使用节流策略避免持续运动被防抖吞掉。
|
|
6922
7250
|
// 避免在持续运动中因防抖重置定时器而无法触发
|
|
6923
|
-
if (source === 'roam' || source === 'firstPersonMove') {
|
|
7251
|
+
if (source === 'roam' || source === 'firstPersonMove' || source === 'thirdPersonMove') {
|
|
6924
7252
|
return { type: 'throttle', limit: 1000 };
|
|
6925
7253
|
}
|
|
6926
7254
|
if (source === 'controlMove') {
|
|
@@ -7077,13 +7405,13 @@ export default {
|
|
|
7077
7405
|
drawModel(data, color = '', meshNameConfig = {}, options = {}) {
|
|
7078
7406
|
// 只打印规模摘要:直接打印 data 会让 DevTools 控制台长期持有整批数据(含全量材质字典)的引用,
|
|
7079
7407
|
// 分批加载期间这些引用在控制台清空前无法回收,会抬高内存高水位。
|
|
7408
|
+
// data = require('./mock.json');
|
|
7080
7409
|
console.log('drawModel', {
|
|
7081
7410
|
material: data.material || [],
|
|
7082
7411
|
mergeMaterial: data.mergeMaterial || [],
|
|
7083
7412
|
primitive: data.primitive || [],
|
|
7084
7413
|
mesh: data.mesh || [],
|
|
7085
7414
|
});
|
|
7086
|
-
// data = require('./mock.json');
|
|
7087
7415
|
// 使用新的分帧加载方法,提供进度回调
|
|
7088
7416
|
return this.drawModelWithBatchLoading(
|
|
7089
7417
|
data,
|
|
@@ -7520,6 +7848,7 @@ export default {
|
|
|
7520
7848
|
this.outlinePass.setSize(width, height);
|
|
7521
7849
|
}
|
|
7522
7850
|
this.labelRenderer.setSize(width, height);
|
|
7851
|
+
this.updateGlobalClippingBoundsResolution(width, height);
|
|
7523
7852
|
// this.timeRender()
|
|
7524
7853
|
// 这里也要更新测量
|
|
7525
7854
|
if (this.threeMeasure) {
|
|
@@ -7755,6 +8084,31 @@ export default {
|
|
|
7755
8084
|
}
|
|
7756
8085
|
}
|
|
7757
8086
|
},
|
|
8087
|
+
captureBusinessVisibilityState() {
|
|
8088
|
+
const modelState = this.noObserver
|
|
8089
|
+
? this.noObserver.modelStateManager
|
|
8090
|
+
: this.modelStateManager;
|
|
8091
|
+
return {
|
|
8092
|
+
defaultBusinessVisible: modelState.defaultBusinessVisible,
|
|
8093
|
+
businessVisibilityOverrides: new Map(modelState.businessVisibilityOverrides),
|
|
8094
|
+
};
|
|
8095
|
+
},
|
|
8096
|
+
restoreBusinessVisibilityState(state) {
|
|
8097
|
+
if (!state) return;
|
|
8098
|
+
const modelState = this.noObserver
|
|
8099
|
+
? this.noObserver.modelStateManager
|
|
8100
|
+
: this.modelStateManager;
|
|
8101
|
+
modelState.defaultBusinessVisible = state.defaultBusinessVisible;
|
|
8102
|
+
modelState.businessVisibilityOverrides = new Map(state.businessVisibilityOverrides || []);
|
|
8103
|
+
this.syncBusinessVisibilityByIds(
|
|
8104
|
+
Array.from(new Set([
|
|
8105
|
+
...Array.from((this._boxIndex && this._boxIndex.keys()) || []),
|
|
8106
|
+
...Array.from(modelState.businessVisibilityOverrides.keys()),
|
|
8107
|
+
]))
|
|
8108
|
+
);
|
|
8109
|
+
this.notifyCameraChange();
|
|
8110
|
+
if (this._boxIndex) this.buildOctreeFromBoxIndex();
|
|
8111
|
+
},
|
|
7758
8112
|
setAllModelVisible(visible) {
|
|
7759
8113
|
const isVisible = Boolean(visible);
|
|
7760
8114
|
const modelState = this.noObserver
|
|
@@ -8513,7 +8867,9 @@ export default {
|
|
|
8513
8867
|
},
|
|
8514
8868
|
// 销毁场景 释放内存
|
|
8515
8869
|
destroyScene() {
|
|
8870
|
+
this.endThirdPersonRoaming();
|
|
8516
8871
|
cancelAnimationFrame(this.animateId);
|
|
8872
|
+
this.disposeOrientationAxis();
|
|
8517
8873
|
this.disposeTransformControls();
|
|
8518
8874
|
|
|
8519
8875
|
if (this.sceneCommandEventDisposers.length > 0) {
|
|
@@ -8589,6 +8945,7 @@ export default {
|
|
|
8589
8945
|
this.resetBusinessVisibilityState();
|
|
8590
8946
|
this.hideCollisionObbDebug();
|
|
8591
8947
|
this.hideOcclusionBoundsDebug();
|
|
8948
|
+
this.removeGlobalClippingBoundsHelper();
|
|
8592
8949
|
|
|
8593
8950
|
if (this.scene) {
|
|
8594
8951
|
this.removeTraverse();
|
|
@@ -8733,6 +9090,565 @@ export default {
|
|
|
8733
9090
|
this.clock = new this.THREE.Clock();
|
|
8734
9091
|
}
|
|
8735
9092
|
},
|
|
9093
|
+
openRoamingSettings(config) {
|
|
9094
|
+
const currentConfig = config ? { ...this.roamingSettings, ...config } : this.roamingSettings;
|
|
9095
|
+
this.roamingSettingsDraft = copyRoamingSettings(currentConfig);
|
|
9096
|
+
this.roamingSettingsCloseReason = '';
|
|
9097
|
+
this.roamingSettingsVisible = true;
|
|
9098
|
+
return this.getRoamingConfig();
|
|
9099
|
+
},
|
|
9100
|
+
getRoamingConfig() {
|
|
9101
|
+
return copyRoamingSettings(this.roamingSettings);
|
|
9102
|
+
},
|
|
9103
|
+
closeRoamingSettings() {
|
|
9104
|
+
if (!this.roamingSettingsVisible) return;
|
|
9105
|
+
this.roamingSettingsCloseReason = 'api';
|
|
9106
|
+
this.roamingSettingsVisible = false;
|
|
9107
|
+
},
|
|
9108
|
+
cancelRoamingSettings() {
|
|
9109
|
+
if (!this.roamingSettingsVisible) return;
|
|
9110
|
+
this.roamingSettingsCloseReason = 'cancel';
|
|
9111
|
+
this.roamingSettingsVisible = false;
|
|
9112
|
+
},
|
|
9113
|
+
handleRoamingSettingsDialogClose() {
|
|
9114
|
+
if (!this.roamingSettingsCloseReason) {
|
|
9115
|
+
this.roamingSettingsCloseReason = 'cancel';
|
|
9116
|
+
}
|
|
9117
|
+
if (this.roamingSettingsCloseReason !== 'confirm') {
|
|
9118
|
+
this.$emit('roaming-settings-cancel');
|
|
9119
|
+
}
|
|
9120
|
+
this.roamingSettingsDraft = null;
|
|
9121
|
+
this.roamingSettingsCloseReason = '';
|
|
9122
|
+
},
|
|
9123
|
+
showRoamingSettingsError(message) {
|
|
9124
|
+
this.$emit('roaming-settings-error', { message });
|
|
9125
|
+
if (this.$message && typeof this.$message.warning === 'function') {
|
|
9126
|
+
this.$message.warning(message);
|
|
9127
|
+
}
|
|
9128
|
+
},
|
|
9129
|
+
validateRoamingSettings() {
|
|
9130
|
+
const draft = this.roamingSettingsDraft;
|
|
9131
|
+
if (!draft) return null;
|
|
9132
|
+
|
|
9133
|
+
const height = Number(draft.height);
|
|
9134
|
+
const moveSpeed = Number(draft.moveSpeed);
|
|
9135
|
+
const mouseSpeed = Number(draft.mouseSpeed);
|
|
9136
|
+
const {
|
|
9137
|
+
height: heightLimit,
|
|
9138
|
+
moveSpeed: moveSpeedLimit,
|
|
9139
|
+
mouseSpeed: mouseSpeedLimit,
|
|
9140
|
+
} = ROAMING_SETTINGS_LIMITS;
|
|
9141
|
+
|
|
9142
|
+
if (draft.height === '' || draft.height === null || !Number.isFinite(height)) {
|
|
9143
|
+
this.showRoamingSettingsError('请输入人物身高');
|
|
9144
|
+
return null;
|
|
9145
|
+
}
|
|
9146
|
+
if (height < heightLimit.min || height > heightLimit.max) {
|
|
9147
|
+
this.showRoamingSettingsError(
|
|
9148
|
+
`人物身高需在 ${heightLimit.min}-${heightLimit.max} cm 范围内`
|
|
9149
|
+
);
|
|
9150
|
+
return null;
|
|
9151
|
+
}
|
|
9152
|
+
if (draft.moveSpeed === '' || draft.moveSpeed === null || !Number.isFinite(moveSpeed)) {
|
|
9153
|
+
this.showRoamingSettingsError('请输入移动速度');
|
|
9154
|
+
return null;
|
|
9155
|
+
}
|
|
9156
|
+
if (moveSpeed < moveSpeedLimit.min || moveSpeed > moveSpeedLimit.max) {
|
|
9157
|
+
this.showRoamingSettingsError(
|
|
9158
|
+
`移动速度需在 ${moveSpeedLimit.min}-${moveSpeedLimit.max} 范围内`
|
|
9159
|
+
);
|
|
9160
|
+
return null;
|
|
9161
|
+
}
|
|
9162
|
+
if (draft.mouseSpeed === '' || draft.mouseSpeed === null || !Number.isFinite(mouseSpeed)) {
|
|
9163
|
+
this.showRoamingSettingsError('请输入鼠标速度');
|
|
9164
|
+
return null;
|
|
9165
|
+
}
|
|
9166
|
+
if (mouseSpeed < mouseSpeedLimit.min || mouseSpeed > mouseSpeedLimit.max) {
|
|
9167
|
+
this.showRoamingSettingsError(
|
|
9168
|
+
`鼠标速度需在 ${mouseSpeedLimit.min}-${mouseSpeedLimit.max} 范围内`
|
|
9169
|
+
);
|
|
9170
|
+
return null;
|
|
9171
|
+
}
|
|
9172
|
+
if (draft.perspective !== 'first-person' && draft.perspective !== 'third-person') {
|
|
9173
|
+
this.showRoamingSettingsError('请选择漫游视角');
|
|
9174
|
+
return null;
|
|
9175
|
+
}
|
|
9176
|
+
if (typeof draft.collisionEnabled !== 'boolean') {
|
|
9177
|
+
this.showRoamingSettingsError('请选择是否设置障碍');
|
|
9178
|
+
return null;
|
|
9179
|
+
}
|
|
9180
|
+
|
|
9181
|
+
return {
|
|
9182
|
+
perspective: draft.perspective,
|
|
9183
|
+
height,
|
|
9184
|
+
collisionEnabled: draft.collisionEnabled,
|
|
9185
|
+
moveSpeed,
|
|
9186
|
+
mouseSpeed,
|
|
9187
|
+
};
|
|
9188
|
+
},
|
|
9189
|
+
confirmRoamingSettings() {
|
|
9190
|
+
const settings = this.validateRoamingSettings();
|
|
9191
|
+
if (!settings) return;
|
|
9192
|
+
|
|
9193
|
+
this.roamingSettings = settings;
|
|
9194
|
+
this.roamingSettingsCloseReason = 'confirm';
|
|
9195
|
+
this.roamingSettingsVisible = false;
|
|
9196
|
+
this.$emit('roaming-settings-confirm', this.getRoamingConfig());
|
|
9197
|
+
},
|
|
9198
|
+
startRoaming() {
|
|
9199
|
+
const settings = this.getRoamingConfig();
|
|
9200
|
+
if (settings.perspective === 'third-person') {
|
|
9201
|
+
return this.startThirdPersonRoaming(settings);
|
|
9202
|
+
}
|
|
9203
|
+
this.endThirdPersonRoaming();
|
|
9204
|
+
this.startFirstPer({
|
|
9205
|
+
moveSpeed: 8000 * settings.moveSpeed,
|
|
9206
|
+
jumpSpeed: 500,
|
|
9207
|
+
mouseSpeed: settings.mouseSpeed,
|
|
9208
|
+
height: settings.height,
|
|
9209
|
+
collisionEnabled: settings.collisionEnabled,
|
|
9210
|
+
});
|
|
9211
|
+
this.$emit('roaming-start', settings);
|
|
9212
|
+
return true;
|
|
9213
|
+
},
|
|
9214
|
+
isRoamingActive() {
|
|
9215
|
+
return !!(this.firstPerSign || this._thirdPersonState);
|
|
9216
|
+
},
|
|
9217
|
+
startThirdPersonRoaming(settings) {
|
|
9218
|
+
if (!this.scene || !this.camera || !this.cameraControls || !this.renderer) {
|
|
9219
|
+
this.showRoamingSettingsError('模型场景尚未准备完成,暂时无法开始第三人称漫游');
|
|
9220
|
+
return false;
|
|
9221
|
+
}
|
|
9222
|
+
this.endThirdPersonRoaming();
|
|
9223
|
+
if (this.pointControls && this.pointControls.isLocked) {
|
|
9224
|
+
this.pointControls.unlock();
|
|
9225
|
+
}
|
|
9226
|
+
|
|
9227
|
+
const config = copyRoamingSettings(settings || this.roamingSettings);
|
|
9228
|
+
const sceneBox = this.sceneBoundingBox;
|
|
9229
|
+
if (!sceneBox || sceneBox.isEmpty()) {
|
|
9230
|
+
this.showRoamingSettingsError('模型空间范围尚未就绪,暂时无法开始第三人称漫游');
|
|
9231
|
+
return false;
|
|
9232
|
+
}
|
|
9233
|
+
|
|
9234
|
+
const target = new this.THREE.Vector3();
|
|
9235
|
+
if (typeof this.cameraControls.getTarget === 'function') {
|
|
9236
|
+
this.cameraControls.getTarget(target);
|
|
9237
|
+
} else if (this.cameraControls._target) {
|
|
9238
|
+
target.copy(this.cameraControls._target);
|
|
9239
|
+
} else {
|
|
9240
|
+
this.camera.getWorldDirection(target);
|
|
9241
|
+
target.multiplyScalar(800).add(this.camera.position);
|
|
9242
|
+
}
|
|
9243
|
+
const direction = this.camera.getWorldDirection(new this.THREE.Vector3());
|
|
9244
|
+
const yaw = Math.atan2(-direction.x, -direction.z);
|
|
9245
|
+
const actorHeight = config.height * 10;
|
|
9246
|
+
const actorPosition = new this.THREE.Vector3(
|
|
9247
|
+
target.x,
|
|
9248
|
+
this.getThirdPersonSpawnHeight(target.x, target.z, sceneBox),
|
|
9249
|
+
target.z
|
|
9250
|
+
);
|
|
9251
|
+
|
|
9252
|
+
this._thirdPersonState = {
|
|
9253
|
+
config,
|
|
9254
|
+
moveSpeed: this.getFirstPersonMoveSpeed(8000 * config.moveSpeed),
|
|
9255
|
+
actorPosition,
|
|
9256
|
+
actorHeight,
|
|
9257
|
+
verticalVelocity: 0,
|
|
9258
|
+
yaw,
|
|
9259
|
+
// worker.glb 的默认正面朝向与相机方向一致,进入第三人称时旋转 180 度背对镜头。
|
|
9260
|
+
avatarYaw: yaw + Math.PI,
|
|
9261
|
+
pitch: 0.22,
|
|
9262
|
+
cameraDistance: (THIRD_PERSON_CAMERA_DISTANCE * config.height) / 168,
|
|
9263
|
+
cameraPosition: this.camera.position.clone(),
|
|
9264
|
+
cameraTarget: target.clone(),
|
|
9265
|
+
avatar: null,
|
|
9266
|
+
walkAction: null,
|
|
9267
|
+
walking: false,
|
|
9268
|
+
loading: true,
|
|
9269
|
+
pointerLocked: false,
|
|
9270
|
+
};
|
|
9271
|
+
this.cameraControls.enabled = false;
|
|
9272
|
+
const initialDistance = this._thirdPersonState.cameraDistance;
|
|
9273
|
+
const initialPitch = this._thirdPersonState.pitch;
|
|
9274
|
+
const initialPosition = new this.THREE.Vector3(
|
|
9275
|
+
target.x + Math.sin(yaw) * Math.cos(initialPitch) * initialDistance,
|
|
9276
|
+
target.y + Math.sin(initialPitch) * initialDistance + actorHeight * 0.15,
|
|
9277
|
+
target.z + Math.cos(yaw) * Math.cos(initialPitch) * initialDistance
|
|
9278
|
+
);
|
|
9279
|
+
this._thirdPersonState.cameraPosition.copy(initialPosition);
|
|
9280
|
+
this.camera.position.copy(initialPosition);
|
|
9281
|
+
this.camera.lookAt(target);
|
|
9282
|
+
this.camera.updateMatrixWorld();
|
|
9283
|
+
this.detachTransformControls();
|
|
9284
|
+
this.unbindScenePointerEvents();
|
|
9285
|
+
document.addEventListener('keydown', this.onThirdPersonKeyDown, false);
|
|
9286
|
+
document.addEventListener('keyup', this.onThirdPersonKeyUp, false);
|
|
9287
|
+
this._thirdPersonMouseMoveHandler = event => this.onThirdPersonMouseMove(event);
|
|
9288
|
+
this._thirdPersonWheelHandler = event => this.onThirdPersonWheel(event);
|
|
9289
|
+
this._thirdPersonPointerLockHandler = () => this.handleThirdPersonPointerLockChange();
|
|
9290
|
+
this._thirdPersonPointerErrorHandler = () => {
|
|
9291
|
+
this.showRoamingSettingsError('浏览器未能锁定鼠标,请点击模型视口后重试');
|
|
9292
|
+
this.endThirdPersonRoaming();
|
|
9293
|
+
};
|
|
9294
|
+
document.addEventListener('mousemove', this._thirdPersonMouseMoveHandler, false);
|
|
9295
|
+
if (this.renderer.domElement) {
|
|
9296
|
+
this.renderer.domElement.addEventListener('wheel', this._thirdPersonWheelHandler, {
|
|
9297
|
+
capture: true,
|
|
9298
|
+
passive: false,
|
|
9299
|
+
});
|
|
9300
|
+
}
|
|
9301
|
+
document.addEventListener('pointerlockchange', this._thirdPersonPointerLockHandler, false);
|
|
9302
|
+
document.addEventListener('pointerlockerror', this._thirdPersonPointerErrorHandler, false);
|
|
9303
|
+
if (this.renderer.domElement && this.renderer.domElement.requestPointerLock) {
|
|
9304
|
+
try {
|
|
9305
|
+
this.renderer.domElement.requestPointerLock();
|
|
9306
|
+
} catch (error) {
|
|
9307
|
+
// 部分浏览器会拒绝自动锁定,仍可通过键盘移动并在视口内点击后获得鼠标控制。
|
|
9308
|
+
}
|
|
9309
|
+
}
|
|
9310
|
+
|
|
9311
|
+
const loader = new GLTFLoader();
|
|
9312
|
+
loader.load(
|
|
9313
|
+
workerModelUrl,
|
|
9314
|
+
gltf => {
|
|
9315
|
+
const state = this._thirdPersonState;
|
|
9316
|
+
if (!state || !this.scene) return;
|
|
9317
|
+
const avatar = gltf.scene;
|
|
9318
|
+
avatar.name = THIRD_PERSON_MODEL_NAME;
|
|
9319
|
+
avatar.updateMatrixWorld(true);
|
|
9320
|
+
const originalBox = new this.THREE.Box3().setFromObject(avatar);
|
|
9321
|
+
const originalHeight = Math.max(originalBox.max.y - originalBox.min.y, 1e-6);
|
|
9322
|
+
const scale = state.actorHeight / originalHeight;
|
|
9323
|
+
avatar.scale.setScalar(scale);
|
|
9324
|
+
state.avatarYOffset = -originalBox.min.y * scale;
|
|
9325
|
+
avatar.position.y = state.avatarYOffset + state.actorPosition.y;
|
|
9326
|
+
avatar.position.x = state.actorPosition.x;
|
|
9327
|
+
avatar.position.z = state.actorPosition.z;
|
|
9328
|
+
avatar.rotation.y = state.avatarYaw;
|
|
9329
|
+
avatar.traverse(object => {
|
|
9330
|
+
if (object.isMesh) {
|
|
9331
|
+
object.frustumCulled = false;
|
|
9332
|
+
object.userData.cull = false;
|
|
9333
|
+
}
|
|
9334
|
+
});
|
|
9335
|
+
state.avatar = avatar;
|
|
9336
|
+
state.loading = false;
|
|
9337
|
+
this.scene.add(avatar);
|
|
9338
|
+
if (gltf.animations && gltf.animations.length) {
|
|
9339
|
+
this._thirdPersonMixer = new this.THREE.AnimationMixer(avatar);
|
|
9340
|
+
state.walkAction = this._thirdPersonMixer.clipAction(gltf.animations[0]);
|
|
9341
|
+
state.walkAction.clampWhenFinished = false;
|
|
9342
|
+
state.walkAction.enabled = true;
|
|
9343
|
+
state.walkAction.stop();
|
|
9344
|
+
}
|
|
9345
|
+
this.$emit('roaming-start', this.getRoamingConfig());
|
|
9346
|
+
},
|
|
9347
|
+
undefined,
|
|
9348
|
+
error => {
|
|
9349
|
+
console.error('第三人称人物模型加载失败', error);
|
|
9350
|
+
this.showRoamingSettingsError('第三人称人物模型加载失败,请检查内置资源');
|
|
9351
|
+
this.endThirdPersonRoaming();
|
|
9352
|
+
}
|
|
9353
|
+
);
|
|
9354
|
+
return true;
|
|
9355
|
+
},
|
|
9356
|
+
endRoaming() {
|
|
9357
|
+
if (this._thirdPersonState) {
|
|
9358
|
+
this.endThirdPersonRoaming();
|
|
9359
|
+
return;
|
|
9360
|
+
}
|
|
9361
|
+
const wasFirstPersonActive = this.firstPerSign;
|
|
9362
|
+
const wasPointerLocked = !!(this.pointControls && this.pointControls.isLocked);
|
|
9363
|
+
if (this.pointControls) {
|
|
9364
|
+
if (this.pointControls.isLocked) this.pointControls.unlock();
|
|
9365
|
+
if (this.pointControls.object && this.pointControls.object.parent === this.scene) {
|
|
9366
|
+
this.scene.remove(this.pointControls.object);
|
|
9367
|
+
}
|
|
9368
|
+
}
|
|
9369
|
+
window.removeEventListener('keydown', this.onKeyDown);
|
|
9370
|
+
window.removeEventListener('keyup', this.onKeyUp);
|
|
9371
|
+
this.pointControls = null;
|
|
9372
|
+
if (this.cameraControls) this.cameraControls.enabled = true;
|
|
9373
|
+
if (this.renderer && this.renderer.domElement) this.bindScenePointerEvents();
|
|
9374
|
+
this.firstPerSign = false;
|
|
9375
|
+
if (!wasFirstPersonActive || !wasPointerLocked) this.$emit('roaming-end');
|
|
9376
|
+
},
|
|
9377
|
+
endThirdPersonRoaming() {
|
|
9378
|
+
const state = this._thirdPersonState;
|
|
9379
|
+
if (!state) return;
|
|
9380
|
+
document.removeEventListener('keydown', this.onThirdPersonKeyDown, false);
|
|
9381
|
+
document.removeEventListener('keyup', this.onThirdPersonKeyUp, false);
|
|
9382
|
+
if (this._thirdPersonMouseMoveHandler) {
|
|
9383
|
+
document.removeEventListener('mousemove', this._thirdPersonMouseMoveHandler, false);
|
|
9384
|
+
}
|
|
9385
|
+
if (this._thirdPersonWheelHandler && this.renderer && this.renderer.domElement) {
|
|
9386
|
+
this.renderer.domElement.removeEventListener('wheel', this._thirdPersonWheelHandler, true);
|
|
9387
|
+
}
|
|
9388
|
+
if (this._thirdPersonPointerLockHandler) {
|
|
9389
|
+
document.removeEventListener(
|
|
9390
|
+
'pointerlockchange',
|
|
9391
|
+
this._thirdPersonPointerLockHandler,
|
|
9392
|
+
false
|
|
9393
|
+
);
|
|
9394
|
+
}
|
|
9395
|
+
if (this._thirdPersonPointerErrorHandler) {
|
|
9396
|
+
document.removeEventListener(
|
|
9397
|
+
'pointerlockerror',
|
|
9398
|
+
this._thirdPersonPointerErrorHandler,
|
|
9399
|
+
false
|
|
9400
|
+
);
|
|
9401
|
+
}
|
|
9402
|
+
const avatar = state.avatar;
|
|
9403
|
+
if (avatar && avatar.parent) avatar.parent.remove(avatar);
|
|
9404
|
+
if (avatar) {
|
|
9405
|
+
avatar.traverse(object => {
|
|
9406
|
+
if (!object.isMesh) return;
|
|
9407
|
+
object.geometry && object.geometry.dispose();
|
|
9408
|
+
const materials = Array.isArray(object.material) ? object.material : [object.material];
|
|
9409
|
+
materials.forEach(material => material && material.dispose());
|
|
9410
|
+
});
|
|
9411
|
+
}
|
|
9412
|
+
if (this._thirdPersonMixer && avatar) this._thirdPersonMixer.uncacheRoot(avatar);
|
|
9413
|
+
this._thirdPersonMixer = null;
|
|
9414
|
+
this._thirdPersonMouseMoveHandler = null;
|
|
9415
|
+
this._thirdPersonWheelHandler = null;
|
|
9416
|
+
this._thirdPersonPointerLockHandler = null;
|
|
9417
|
+
this._thirdPersonPointerErrorHandler = null;
|
|
9418
|
+
if (this.cameraControls && this.camera && state.cameraTarget) {
|
|
9419
|
+
this.cameraControls.setLookAt(
|
|
9420
|
+
this.camera.position.x,
|
|
9421
|
+
this.camera.position.y,
|
|
9422
|
+
this.camera.position.z,
|
|
9423
|
+
state.cameraTarget.x,
|
|
9424
|
+
state.cameraTarget.y,
|
|
9425
|
+
state.cameraTarget.z,
|
|
9426
|
+
false
|
|
9427
|
+
);
|
|
9428
|
+
}
|
|
9429
|
+
this._thirdPersonState = null;
|
|
9430
|
+
if (this.cameraControls) this.cameraControls.enabled = true;
|
|
9431
|
+
if (
|
|
9432
|
+
this.renderer &&
|
|
9433
|
+
this.renderer.domElement &&
|
|
9434
|
+
document.pointerLockElement === this.renderer.domElement
|
|
9435
|
+
) {
|
|
9436
|
+
document.exitPointerLock && document.exitPointerLock();
|
|
9437
|
+
}
|
|
9438
|
+
if (this.renderer && this.renderer.domElement) this.bindScenePointerEvents();
|
|
9439
|
+
if (this.scene) this.ensureTransformSelectionValid();
|
|
9440
|
+
this.$emit('roaming-end');
|
|
9441
|
+
},
|
|
9442
|
+
onThirdPersonKeyDown(event) {
|
|
9443
|
+
if (!this._thirdPersonState || !event) return;
|
|
9444
|
+
if (![37, 38, 39, 40, 65, 68, 83, 87].includes(event.keyCode)) return;
|
|
9445
|
+
event.preventDefault();
|
|
9446
|
+
if (event.keyCode === 38 || event.keyCode === 87) this.moveForward = true;
|
|
9447
|
+
if (event.keyCode === 40 || event.keyCode === 83) this.moveBackward = true;
|
|
9448
|
+
if (event.keyCode === 37 || event.keyCode === 65) this.moveLeft = true;
|
|
9449
|
+
if (event.keyCode === 39 || event.keyCode === 68) this.moveRight = true;
|
|
9450
|
+
},
|
|
9451
|
+
onThirdPersonKeyUp(event) {
|
|
9452
|
+
if (!this._thirdPersonState || !event) return;
|
|
9453
|
+
this.onKeyUp(event);
|
|
9454
|
+
},
|
|
9455
|
+
onThirdPersonMouseMove(event) {
|
|
9456
|
+
const state = this._thirdPersonState;
|
|
9457
|
+
if (!state || document.pointerLockElement !== this.renderer.domElement) return;
|
|
9458
|
+
const sensitivity = state.config.mouseSpeed * 0.0018;
|
|
9459
|
+
state.yaw -= (event.movementX || 0) * sensitivity;
|
|
9460
|
+
state.pitch = Math.max(
|
|
9461
|
+
-0.2,
|
|
9462
|
+
Math.min(0.8, state.pitch + (event.movementY || 0) * sensitivity)
|
|
9463
|
+
);
|
|
9464
|
+
},
|
|
9465
|
+
onThirdPersonWheel(event) {
|
|
9466
|
+
const state = this._thirdPersonState;
|
|
9467
|
+
if (!state || !event) return;
|
|
9468
|
+
event.preventDefault();
|
|
9469
|
+
event.stopPropagation();
|
|
9470
|
+
if (typeof event.stopImmediatePropagation === 'function') event.stopImmediatePropagation();
|
|
9471
|
+
const baseDistance = (THIRD_PERSON_CAMERA_DISTANCE * state.config.height) / 168;
|
|
9472
|
+
const minDistance = Math.max(state.actorHeight * THIRD_PERSON_CAMERA_MIN_DISTANCE_RATIO, 20);
|
|
9473
|
+
const maxDistance = baseDistance * THIRD_PERSON_CAMERA_MAX_DISTANCE_MULTIPLIER;
|
|
9474
|
+
const delta = Math.max(-240, Math.min(240, event.deltaY || 0));
|
|
9475
|
+
state.cameraDistance = Math.max(
|
|
9476
|
+
minDistance,
|
|
9477
|
+
Math.min(maxDistance, state.cameraDistance + delta * Math.max(baseDistance * 0.001, 0.5))
|
|
9478
|
+
);
|
|
9479
|
+
},
|
|
9480
|
+
handleThirdPersonPointerLockChange() {
|
|
9481
|
+
const state = this._thirdPersonState;
|
|
9482
|
+
if (!state || !this.renderer || !this.renderer.domElement) return;
|
|
9483
|
+
if (document.pointerLockElement === this.renderer.domElement) {
|
|
9484
|
+
state.pointerLocked = true;
|
|
9485
|
+
} else if (state.pointerLocked) this.endThirdPersonRoaming();
|
|
9486
|
+
},
|
|
9487
|
+
canMoveThirdPersonTo(position) {
|
|
9488
|
+
const state = this._thirdPersonState;
|
|
9489
|
+
if (!state || !state.config.collisionEnabled) return true;
|
|
9490
|
+
if (!this._octree && this._boxIndex && this._boxIndex.size) this.buildOctreeFromBoxIndex();
|
|
9491
|
+
const radius = state.actorHeight * THIRD_PERSON_COLLISION_RADIUS_RATIO;
|
|
9492
|
+
const bodyBox = new this.THREE.Box3(
|
|
9493
|
+
new this.THREE.Vector3(
|
|
9494
|
+
position.x - radius,
|
|
9495
|
+
position.y + state.actorHeight * 0.12,
|
|
9496
|
+
position.z - radius
|
|
9497
|
+
),
|
|
9498
|
+
new this.THREE.Vector3(
|
|
9499
|
+
position.x + radius,
|
|
9500
|
+
position.y + state.actorHeight * 0.9,
|
|
9501
|
+
position.z + radius
|
|
9502
|
+
)
|
|
9503
|
+
);
|
|
9504
|
+
const candidates = this._queryOctreeByBox(bodyBox);
|
|
9505
|
+
return !candidates.some(
|
|
9506
|
+
item => item && item.box && item.box.userData && item.box.userData.visible !== false
|
|
9507
|
+
);
|
|
9508
|
+
},
|
|
9509
|
+
getThirdPersonSpawnHeight(x, z, sceneBox) {
|
|
9510
|
+
if (!sceneBox || sceneBox.isEmpty() || !this.scene) return 0;
|
|
9511
|
+
const origin = new this.THREE.Vector3(x, sceneBox.max.y + 1, z);
|
|
9512
|
+
const raycaster = new this.THREE.Raycaster(
|
|
9513
|
+
origin,
|
|
9514
|
+
new this.THREE.Vector3(0, -1, 0),
|
|
9515
|
+
0,
|
|
9516
|
+
Math.max(sceneBox.max.y - sceneBox.min.y + 2, 1)
|
|
9517
|
+
);
|
|
9518
|
+
const intersections = raycaster.intersectObjects(this.scene.children, true);
|
|
9519
|
+
return intersections.length ? intersections[0].point.y : sceneBox.min.y;
|
|
9520
|
+
},
|
|
9521
|
+
getThirdPersonCameraDistance(state, target, desiredPosition) {
|
|
9522
|
+
if (!this._octree && this._boxIndex && this._boxIndex.size) this.buildOctreeFromBoxIndex();
|
|
9523
|
+
const segmentBox = new this.THREE.Box3().setFromPoints([target, desiredPosition]);
|
|
9524
|
+
const radius = Math.max(state.actorHeight * 0.035, 10);
|
|
9525
|
+
segmentBox.expandByScalar(radius);
|
|
9526
|
+
const ray = new this.THREE.Ray(target, desiredPosition.clone().sub(target).normalize());
|
|
9527
|
+
const desiredDistance = target.distanceTo(desiredPosition);
|
|
9528
|
+
let nearestDistance = desiredDistance;
|
|
9529
|
+
this._queryOctreeByBox(segmentBox).forEach(item => {
|
|
9530
|
+
if (!item || !item.box || (item.box.userData && item.box.userData.visible === false))
|
|
9531
|
+
return;
|
|
9532
|
+
const expandedBox = item.box.clone().expandByScalar(radius);
|
|
9533
|
+
const hit = ray.intersectBox(expandedBox, new this.THREE.Vector3());
|
|
9534
|
+
if (hit) {
|
|
9535
|
+
const distance = target.distanceTo(hit);
|
|
9536
|
+
if (distance > radius && distance < nearestDistance) nearestDistance = distance;
|
|
9537
|
+
}
|
|
9538
|
+
});
|
|
9539
|
+
return Math.max(state.actorHeight * 0.65, nearestDistance - radius);
|
|
9540
|
+
},
|
|
9541
|
+
updateThirdPersonGrounding(state, delta) {
|
|
9542
|
+
if (!state || !state.actorPosition || !this.scene || !this.sceneBoundingBox) return;
|
|
9543
|
+
const position = state.actorPosition;
|
|
9544
|
+
const sceneBox = this.sceneBoundingBox;
|
|
9545
|
+
const rayOriginY = position.y + THIRD_PERSON_GROUND_SNAP_DISTANCE;
|
|
9546
|
+
const rayDistance = Math.max(rayOriginY - sceneBox.min.y, THIRD_PERSON_GROUND_SNAP_DISTANCE);
|
|
9547
|
+
const raycaster = new this.THREE.Raycaster(
|
|
9548
|
+
new this.THREE.Vector3(position.x, rayOriginY, position.z),
|
|
9549
|
+
new this.THREE.Vector3(0, -1, 0),
|
|
9550
|
+
0,
|
|
9551
|
+
rayDistance
|
|
9552
|
+
);
|
|
9553
|
+
const groundObjects = this.scene.children.filter(
|
|
9554
|
+
object => object && object.name !== THIRD_PERSON_MODEL_NAME
|
|
9555
|
+
);
|
|
9556
|
+
const intersections = raycaster.intersectObjects(groundObjects, true);
|
|
9557
|
+
const groundHit = intersections[0];
|
|
9558
|
+
const groundHeight = groundHit ? groundHit.point.y : sceneBox.min.y;
|
|
9559
|
+
const distanceToGround = position.y - groundHeight;
|
|
9560
|
+
const snapDistance = Math.max(THIRD_PERSON_GROUND_SNAP_DISTANCE, state.actorHeight * 0.015);
|
|
9561
|
+
|
|
9562
|
+
if (distanceToGround <= snapDistance && state.verticalVelocity <= 0) {
|
|
9563
|
+
position.y = groundHeight;
|
|
9564
|
+
state.verticalVelocity = 0;
|
|
9565
|
+
return;
|
|
9566
|
+
}
|
|
9567
|
+
|
|
9568
|
+
state.verticalVelocity = Math.max(
|
|
9569
|
+
state.verticalVelocity - THIRD_PERSON_GRAVITY * delta,
|
|
9570
|
+
-THIRD_PERSON_GRAVITY
|
|
9571
|
+
);
|
|
9572
|
+
const nextY = position.y + state.verticalVelocity * delta;
|
|
9573
|
+
if (nextY <= groundHeight) {
|
|
9574
|
+
position.y = groundHeight;
|
|
9575
|
+
state.verticalVelocity = 0;
|
|
9576
|
+
} else {
|
|
9577
|
+
position.y = nextY;
|
|
9578
|
+
}
|
|
9579
|
+
},
|
|
9580
|
+
updateThirdPersonRoaming(delta) {
|
|
9581
|
+
const state = this._thirdPersonState;
|
|
9582
|
+
if (!state || !state.avatar || state.loading) return;
|
|
9583
|
+
const elapsed = Math.min(Math.max(delta || 0, 0), 0.05);
|
|
9584
|
+
const forward = Number(this.moveForward) - Number(this.moveBackward);
|
|
9585
|
+
const lateral = Number(this.moveRight) - Number(this.moveLeft);
|
|
9586
|
+
const moving = forward !== 0 || lateral !== 0;
|
|
9587
|
+
if (state.walkAction) {
|
|
9588
|
+
if (moving && !state.walking) {
|
|
9589
|
+
state.walkAction.reset().play();
|
|
9590
|
+
state.walking = true;
|
|
9591
|
+
} else if (!moving && state.walking) {
|
|
9592
|
+
state.walkAction.stop();
|
|
9593
|
+
state.walking = false;
|
|
9594
|
+
}
|
|
9595
|
+
}
|
|
9596
|
+
const length = Math.hypot(forward, lateral) || 1;
|
|
9597
|
+
const speed = state.moveSpeed;
|
|
9598
|
+
const step = speed * elapsed;
|
|
9599
|
+
const moveX =
|
|
9600
|
+
((-Math.sin(state.yaw) * forward + Math.cos(state.yaw) * lateral) / length) * step;
|
|
9601
|
+
const moveZ =
|
|
9602
|
+
((-Math.cos(state.yaw) * forward - Math.sin(state.yaw) * lateral) / length) * step;
|
|
9603
|
+
const nextX = state.actorPosition.clone();
|
|
9604
|
+
nextX.x += moveX;
|
|
9605
|
+
if (this.canMoveThirdPersonTo(nextX)) state.actorPosition.x = nextX.x;
|
|
9606
|
+
const nextZ = state.actorPosition.clone();
|
|
9607
|
+
nextZ.z += moveZ;
|
|
9608
|
+
if (this.canMoveThirdPersonTo(nextZ)) state.actorPosition.z = nextZ.z;
|
|
9609
|
+
this.updateThirdPersonGrounding(state, elapsed);
|
|
9610
|
+
|
|
9611
|
+
const avatar = state.avatar;
|
|
9612
|
+
avatar.position.set(
|
|
9613
|
+
state.actorPosition.x,
|
|
9614
|
+
state.actorPosition.y + (state.avatarYOffset || 0),
|
|
9615
|
+
state.actorPosition.z
|
|
9616
|
+
);
|
|
9617
|
+
if (forward !== 0 || lateral !== 0) {
|
|
9618
|
+
// 模型正面沿 +Z,人物朝向实际的相机相对移动方向。
|
|
9619
|
+
state.avatarYaw = Math.atan2(moveX, moveZ);
|
|
9620
|
+
}
|
|
9621
|
+
avatar.rotation.y = state.avatarYaw;
|
|
9622
|
+
if (this._thirdPersonMixer) this._thirdPersonMixer.update(elapsed);
|
|
9623
|
+
|
|
9624
|
+
const target = new this.THREE.Vector3(
|
|
9625
|
+
state.actorPosition.x,
|
|
9626
|
+
state.actorPosition.y + state.actorHeight * THIRD_PERSON_CAMERA_HEIGHT_RATIO,
|
|
9627
|
+
state.actorPosition.z
|
|
9628
|
+
);
|
|
9629
|
+
const horizontalDistance = state.cameraDistance;
|
|
9630
|
+
const pitch = state.pitch;
|
|
9631
|
+
const desiredPosition = new this.THREE.Vector3(
|
|
9632
|
+
target.x + Math.sin(state.yaw) * Math.cos(pitch) * horizontalDistance,
|
|
9633
|
+
target.y + Math.sin(pitch) * horizontalDistance + state.actorHeight * 0.15,
|
|
9634
|
+
target.z + Math.cos(state.yaw) * Math.cos(pitch) * horizontalDistance
|
|
9635
|
+
);
|
|
9636
|
+
const safeDistance = this.getThirdPersonCameraDistance(state, target, desiredPosition);
|
|
9637
|
+
const desiredOffset = desiredPosition
|
|
9638
|
+
.clone()
|
|
9639
|
+
.sub(target)
|
|
9640
|
+
.normalize()
|
|
9641
|
+
.multiplyScalar(safeDistance);
|
|
9642
|
+
desiredPosition.copy(target).add(desiredOffset);
|
|
9643
|
+
const smoothing = 1 - Math.exp(-50 * elapsed);
|
|
9644
|
+
state.cameraPosition.lerp(desiredPosition, smoothing);
|
|
9645
|
+
state.cameraTarget.lerp(target, smoothing);
|
|
9646
|
+
this.camera.position.copy(state.cameraPosition);
|
|
9647
|
+
this.camera.lookAt(state.cameraTarget);
|
|
9648
|
+
this.camera.updateMatrixWorld();
|
|
9649
|
+
if (typeof this._cameraChangeObserver === 'function')
|
|
9650
|
+
this._cameraChangeObserver('thirdPersonMove');
|
|
9651
|
+
},
|
|
8736
9652
|
//
|
|
8737
9653
|
// 开启漫游
|
|
8738
9654
|
/*
|
|
@@ -8861,6 +9777,7 @@ export default {
|
|
|
8861
9777
|
if (userData.transformControlHelper === true) return true;
|
|
8862
9778
|
if (userData.collisionObbDebugHelper === true) return true;
|
|
8863
9779
|
if (userData.occlusionBoundsDebugHelper === true) return true;
|
|
9780
|
+
if (userData.globalClippingBoundsHelper === true) return true;
|
|
8864
9781
|
if (object.isCamera || object.isLight) return true;
|
|
8865
9782
|
if (typeof object.type === 'string' && /Helper$/i.test(object.type)) return true;
|
|
8866
9783
|
return object.type === 'CSS2DObject' || object.type === 'TransformControlsRoot';
|
|
@@ -8882,6 +9799,138 @@ export default {
|
|
|
8882
9799
|
});
|
|
8883
9800
|
return box3.isEmpty() ? null : box3;
|
|
8884
9801
|
},
|
|
9802
|
+
getGlobalClippingBoundsSegments(bounds) {
|
|
9803
|
+
const min = bounds.min;
|
|
9804
|
+
const max = bounds.max;
|
|
9805
|
+
return [
|
|
9806
|
+
[
|
|
9807
|
+
min.x,
|
|
9808
|
+
min.y,
|
|
9809
|
+
min.z,
|
|
9810
|
+
max.x,
|
|
9811
|
+
min.y,
|
|
9812
|
+
min.z,
|
|
9813
|
+
max.x,
|
|
9814
|
+
max.y,
|
|
9815
|
+
min.z,
|
|
9816
|
+
min.x,
|
|
9817
|
+
max.y,
|
|
9818
|
+
min.z,
|
|
9819
|
+
min.x,
|
|
9820
|
+
min.y,
|
|
9821
|
+
min.z,
|
|
9822
|
+
],
|
|
9823
|
+
[
|
|
9824
|
+
min.x,
|
|
9825
|
+
min.y,
|
|
9826
|
+
max.z,
|
|
9827
|
+
max.x,
|
|
9828
|
+
min.y,
|
|
9829
|
+
max.z,
|
|
9830
|
+
max.x,
|
|
9831
|
+
max.y,
|
|
9832
|
+
max.z,
|
|
9833
|
+
min.x,
|
|
9834
|
+
max.y,
|
|
9835
|
+
max.z,
|
|
9836
|
+
min.x,
|
|
9837
|
+
min.y,
|
|
9838
|
+
max.z,
|
|
9839
|
+
],
|
|
9840
|
+
[min.x, min.y, min.z, min.x, min.y, max.z],
|
|
9841
|
+
[max.x, min.y, min.z, max.x, min.y, max.z],
|
|
9842
|
+
[max.x, max.y, min.z, max.x, max.y, max.z],
|
|
9843
|
+
[min.x, max.y, min.z, min.x, max.y, max.z],
|
|
9844
|
+
];
|
|
9845
|
+
},
|
|
9846
|
+
updateGlobalClippingBoundsResolution(width, height) {
|
|
9847
|
+
const helper = this.globalClippingBoundsHelper;
|
|
9848
|
+
const firstLine = helper && helper.children && helper.children[0];
|
|
9849
|
+
const resolution = firstLine && firstLine.material && firstLine.material.resolution;
|
|
9850
|
+
if (!resolution) return;
|
|
9851
|
+
if ((!width || !height) && this.renderer && this.renderer.domElement) {
|
|
9852
|
+
const rect = this.renderer.domElement.getBoundingClientRect();
|
|
9853
|
+
width = rect.width;
|
|
9854
|
+
height = rect.height;
|
|
9855
|
+
}
|
|
9856
|
+
if (width > 0 && height > 0) resolution.set(width, height);
|
|
9857
|
+
},
|
|
9858
|
+
removeGlobalClippingBoundsHelper() {
|
|
9859
|
+
const helper = this.globalClippingBoundsHelper;
|
|
9860
|
+
if (!helper) return;
|
|
9861
|
+
if (this.scene) this.scene.remove(helper);
|
|
9862
|
+
helper.children.forEach(line => {
|
|
9863
|
+
line.geometry && line.geometry.dispose && line.geometry.dispose();
|
|
9864
|
+
});
|
|
9865
|
+
const material = helper.children[0] && helper.children[0].material;
|
|
9866
|
+
material && material.dispose && material.dispose();
|
|
9867
|
+
this.globalClippingBoundsHelper = null;
|
|
9868
|
+
},
|
|
9869
|
+
updateGlobalClippingBoundsHelper() {
|
|
9870
|
+
const planes = this.renderer && this.renderer.clippingPlanes;
|
|
9871
|
+
if (!this.scene || !planes || planes.length < 6) {
|
|
9872
|
+
this.removeGlobalClippingBoundsHelper();
|
|
9873
|
+
return false;
|
|
9874
|
+
}
|
|
9875
|
+
|
|
9876
|
+
const constants = planes.slice(0, 6).map(plane => plane && plane.constant);
|
|
9877
|
+
if (constants.some(value => !Number.isFinite(value))) {
|
|
9878
|
+
this.removeGlobalClippingBoundsHelper();
|
|
9879
|
+
return false;
|
|
9880
|
+
}
|
|
9881
|
+
|
|
9882
|
+
const bounds = new this.THREE.Box3(
|
|
9883
|
+
new this.THREE.Vector3(-constants[3], -constants[4], -constants[5]),
|
|
9884
|
+
new this.THREE.Vector3(constants[0], constants[1], constants[2])
|
|
9885
|
+
);
|
|
9886
|
+
if (bounds.isEmpty()) {
|
|
9887
|
+
this.removeGlobalClippingBoundsHelper();
|
|
9888
|
+
return false;
|
|
9889
|
+
}
|
|
9890
|
+
|
|
9891
|
+
const segments = this.getGlobalClippingBoundsSegments(bounds);
|
|
9892
|
+
let helper = this.globalClippingBoundsHelper;
|
|
9893
|
+
if (!helper) {
|
|
9894
|
+
const material = new MeshLineMaterial({
|
|
9895
|
+
color: 0x999999,
|
|
9896
|
+
lineWidth: 15,
|
|
9897
|
+
sizeAttenuation: 0,
|
|
9898
|
+
transparent: true,
|
|
9899
|
+
opacity: 1,
|
|
9900
|
+
depthTest: false,
|
|
9901
|
+
depthWrite: false,
|
|
9902
|
+
depthFunc: this.THREE.LessEqualDepth,
|
|
9903
|
+
// polygonOffset: true,
|
|
9904
|
+
// polygonOffsetFactor: -1,
|
|
9905
|
+
// polygonOffsetUnits: -1,
|
|
9906
|
+
side: this.THREE.DoubleSide,
|
|
9907
|
+
toneMapped: false,
|
|
9908
|
+
resolution: new this.THREE.Vector2(1, 1),
|
|
9909
|
+
});
|
|
9910
|
+
helper = new this.THREE.Group();
|
|
9911
|
+
helper.name = 'globalClippingBoundsHelper';
|
|
9912
|
+
helper.userData.globalClippingBoundsHelper = true;
|
|
9913
|
+
segments.forEach(points => {
|
|
9914
|
+
const geometry = new MeshLineGeometry();
|
|
9915
|
+
geometry.setPoints(points);
|
|
9916
|
+
const line = new this.THREE.Mesh(geometry, material);
|
|
9917
|
+
line.renderOrder = 1001;
|
|
9918
|
+
line.frustumCulled = false;
|
|
9919
|
+
line.raycast = function () {};
|
|
9920
|
+
helper.add(line);
|
|
9921
|
+
});
|
|
9922
|
+
this.scene.add(helper);
|
|
9923
|
+
this.globalClippingBoundsHelper = helper;
|
|
9924
|
+
} else {
|
|
9925
|
+
helper.children.forEach((line, index) => {
|
|
9926
|
+
line.geometry.setPoints(segments[index]);
|
|
9927
|
+
});
|
|
9928
|
+
if (helper.parent !== this.scene) this.scene.add(helper);
|
|
9929
|
+
}
|
|
9930
|
+
this.updateGlobalClippingBoundsResolution();
|
|
9931
|
+
helper.visible = !this.guiParams || this.guiParams['显示剖切线框'] !== false;
|
|
9932
|
+
return true;
|
|
9933
|
+
},
|
|
8885
9934
|
// 设置全局整体剖切
|
|
8886
9935
|
/*
|
|
8887
9936
|
先开启模型全局剖切模式, 会返回剖切值的最大最小值
|
|
@@ -8890,6 +9939,7 @@ export default {
|
|
|
8890
9939
|
setGlobalClipping(flag = true) {
|
|
8891
9940
|
const box3 = this.getGlobalClippingBox();
|
|
8892
9941
|
if (!box3) {
|
|
9942
|
+
this.removeGlobalClippingBoundsHelper();
|
|
8893
9943
|
return {
|
|
8894
9944
|
min: null,
|
|
8895
9945
|
max: null,
|
|
@@ -8952,6 +10002,7 @@ export default {
|
|
|
8952
10002
|
clippingPlanes
|
|
8953
10003
|
);
|
|
8954
10004
|
}
|
|
10005
|
+
this.updateGlobalClippingBoundsHelper();
|
|
8955
10006
|
return {
|
|
8956
10007
|
min: min,
|
|
8957
10008
|
max: max,
|
|
@@ -8971,6 +10022,7 @@ export default {
|
|
|
8971
10022
|
for (const key in params) {
|
|
8972
10023
|
this.renderer.clippingPlanes[axis[key]].constant = params[key];
|
|
8973
10024
|
}
|
|
10025
|
+
this.updateGlobalClippingBoundsHelper();
|
|
8974
10026
|
// this.timeRender()
|
|
8975
10027
|
},
|
|
8976
10028
|
// 取消全局剖切
|
|
@@ -8985,6 +10037,7 @@ export default {
|
|
|
8985
10037
|
};
|
|
8986
10038
|
this.clippingPlanesConstants = null;
|
|
8987
10039
|
this.renderer.clippingPlanes = Object.freeze([]);
|
|
10040
|
+
this.removeGlobalClippingBoundsHelper();
|
|
8988
10041
|
if (this.gui) {
|
|
8989
10042
|
this.gui.destroy();
|
|
8990
10043
|
this.gui = null;
|
|
@@ -9116,6 +10169,9 @@ export default {
|
|
|
9116
10169
|
'-y轴': yRange.min,
|
|
9117
10170
|
'-z轴': zRange.min,
|
|
9118
10171
|
};
|
|
10172
|
+
if (title === '全局剖切') {
|
|
10173
|
+
this.guiParams['显示剖切线框'] = true;
|
|
10174
|
+
}
|
|
9119
10175
|
|
|
9120
10176
|
const initialConstants = [
|
|
9121
10177
|
xRange.max,
|
|
@@ -9147,7 +10203,22 @@ export default {
|
|
|
9147
10203
|
this.closeGlobalClipping();
|
|
9148
10204
|
});
|
|
9149
10205
|
this.gui.domElement.querySelector('.title').appendChild(closeButton);
|
|
10206
|
+
const wireframeToggle = this.gui
|
|
10207
|
+
.add(this.guiParams, '显示剖切线框')
|
|
10208
|
+
.name('显示剖切线框')
|
|
10209
|
+
.listen();
|
|
10210
|
+
wireframeToggle.domElement.classList.add('global-clipping-wireframe-toggle');
|
|
10211
|
+
const wireframeToggleWidget = wireframeToggle.domElement.querySelector('.widget');
|
|
10212
|
+
if (wireframeToggleWidget) wireframeToggleWidget.classList.remove('widget');
|
|
10213
|
+
wireframeToggle.onChange(visible => {
|
|
10214
|
+
if (this.globalClippingBoundsHelper) {
|
|
10215
|
+
this.globalClippingBoundsHelper.visible = visible;
|
|
10216
|
+
}
|
|
10217
|
+
});
|
|
9150
10218
|
}
|
|
10219
|
+
const updateGlobalClippingBounds = () => {
|
|
10220
|
+
if (title === '全局剖切') this.updateGlobalClippingBoundsHelper();
|
|
10221
|
+
};
|
|
9151
10222
|
this.gui
|
|
9152
10223
|
.add(this.guiParams, 'x轴')
|
|
9153
10224
|
.step(0.001)
|
|
@@ -9156,6 +10227,7 @@ export default {
|
|
|
9156
10227
|
.onChange(d => {
|
|
9157
10228
|
objClipp1[0].constant = d;
|
|
9158
10229
|
objClipp2 && (objClipp2[0].constant = d);
|
|
10230
|
+
updateGlobalClippingBounds();
|
|
9159
10231
|
});
|
|
9160
10232
|
this.gui
|
|
9161
10233
|
.add(this.guiParams, '-x轴')
|
|
@@ -9165,6 +10237,7 @@ export default {
|
|
|
9165
10237
|
.onChange(d => {
|
|
9166
10238
|
objClipp1[3].constant = -d;
|
|
9167
10239
|
objClipp2 && (objClipp2[3].constant = -d);
|
|
10240
|
+
updateGlobalClippingBounds();
|
|
9168
10241
|
});
|
|
9169
10242
|
this.gui
|
|
9170
10243
|
.add(this.guiParams, 'y轴')
|
|
@@ -9174,6 +10247,7 @@ export default {
|
|
|
9174
10247
|
.onChange(d => {
|
|
9175
10248
|
objClipp1[2].constant = d;
|
|
9176
10249
|
objClipp2 && (objClipp2[2].constant = d);
|
|
10250
|
+
updateGlobalClippingBounds();
|
|
9177
10251
|
});
|
|
9178
10252
|
this.gui
|
|
9179
10253
|
.add(this.guiParams, '-y轴')
|
|
@@ -9183,6 +10257,7 @@ export default {
|
|
|
9183
10257
|
.onChange(d => {
|
|
9184
10258
|
objClipp1[5].constant = -d;
|
|
9185
10259
|
objClipp2 && (objClipp2[5].constant = -d);
|
|
10260
|
+
updateGlobalClippingBounds();
|
|
9186
10261
|
});
|
|
9187
10262
|
this.gui
|
|
9188
10263
|
.add(this.guiParams, 'z轴')
|
|
@@ -9192,6 +10267,7 @@ export default {
|
|
|
9192
10267
|
.onChange(d => {
|
|
9193
10268
|
objClipp1[1].constant = d;
|
|
9194
10269
|
objClipp2 && (objClipp2[1].constant = d);
|
|
10270
|
+
updateGlobalClippingBounds();
|
|
9195
10271
|
});
|
|
9196
10272
|
this.gui
|
|
9197
10273
|
.add(this.guiParams, '-z轴')
|
|
@@ -9201,6 +10277,7 @@ export default {
|
|
|
9201
10277
|
.onChange(d => {
|
|
9202
10278
|
objClipp1[4].constant = -d;
|
|
9203
10279
|
objClipp2 && (objClipp2[4].constant = -d);
|
|
10280
|
+
updateGlobalClippingBounds();
|
|
9204
10281
|
});
|
|
9205
10282
|
},
|
|
9206
10283
|
getFirstPersonMoveSpeed(customMoveSpeed) {
|
|
@@ -9265,9 +10342,17 @@ export default {
|
|
|
9265
10342
|
},
|
|
9266
10343
|
// 开启第一视角
|
|
9267
10344
|
startFirstPer(options) {
|
|
9268
|
-
let {
|
|
9269
|
-
|
|
9270
|
-
|
|
10345
|
+
let {
|
|
10346
|
+
moveSpeed = 200,
|
|
10347
|
+
jumpSpeed = 200,
|
|
10348
|
+
mouseSpeed = 1,
|
|
10349
|
+
collisionEnabled = true,
|
|
10350
|
+
height = 168,
|
|
10351
|
+
} = options || {};
|
|
10352
|
+
this.firstPersonMouseSpeed = mouseSpeed;
|
|
10353
|
+
this.firstPersonCollisionEnabled = collisionEnabled;
|
|
10354
|
+
this.firstPersonActorHeight = height * 10;
|
|
10355
|
+
this.removeSpeed = this.getFirstPersonMoveSpeed(moveSpeed);
|
|
9271
10356
|
const jumpConfig = this.getFirstPersonJumpConfig(jumpSpeed);
|
|
9272
10357
|
this.upSpeed = jumpConfig.jumpSpeed;
|
|
9273
10358
|
this.firstPersonGravity = jumpConfig.gravity;
|
|
@@ -9293,7 +10378,8 @@ export default {
|
|
|
9293
10378
|
this.camera.rotation.z = 0;
|
|
9294
10379
|
|
|
9295
10380
|
this.pointControls = new PointerLockControls(this.camera, this.renderer.domElement);
|
|
9296
|
-
this.pointControls.pointerSpeed =
|
|
10381
|
+
this.pointControls.pointerSpeed =
|
|
10382
|
+
FIRST_PERSON_POINTER_SPEED * (this.firstPersonMouseSpeed || 1);
|
|
9297
10383
|
this.pointControls.lock();
|
|
9298
10384
|
if (!this._onFirstPersonChange) {
|
|
9299
10385
|
this._onFirstPersonChange = () => {
|
|
@@ -9320,6 +10406,7 @@ export default {
|
|
|
9320
10406
|
});
|
|
9321
10407
|
// 解锁
|
|
9322
10408
|
this.pointControls.addEventListener('unlock', () => {
|
|
10409
|
+
const wasFirstPersonActive = this.firstPerSign;
|
|
9323
10410
|
this.firstPerSign = false;
|
|
9324
10411
|
if (this.cameraControls) {
|
|
9325
10412
|
this.cameraControls.enabled = true;
|
|
@@ -9350,6 +10437,7 @@ export default {
|
|
|
9350
10437
|
if (typeof this._cameraChangeObserver === 'function') {
|
|
9351
10438
|
this._cameraChangeObserver('firstPersonUnlock');
|
|
9352
10439
|
}
|
|
10440
|
+
if (wasFirstPersonActive) this.$emit('roaming-end');
|
|
9353
10441
|
});
|
|
9354
10442
|
if (this.scene) this.scene.add(this.pointControls.object);
|
|
9355
10443
|
}, 10);
|
|
@@ -9391,9 +10479,24 @@ export default {
|
|
|
9391
10479
|
this.velocity.y = Math.max(0, this.velocity.y);
|
|
9392
10480
|
this.canJump = true;
|
|
9393
10481
|
}
|
|
9394
|
-
//
|
|
9395
|
-
|
|
9396
|
-
|
|
10482
|
+
// 分轴应用移动,避免合并位移的碰撞结果阻断可通行的另一个方向。
|
|
10483
|
+
const yaw = control.rotation.y;
|
|
10484
|
+
const rightDistance = -this.velocity.x * delta;
|
|
10485
|
+
const forwardDistance = -this.velocity.z * delta;
|
|
10486
|
+
const nextRightPosition = control.position.clone();
|
|
10487
|
+
nextRightPosition.x += Math.cos(yaw) * rightDistance;
|
|
10488
|
+
nextRightPosition.z += -Math.sin(yaw) * rightDistance;
|
|
10489
|
+
if (this.canMoveFirstPersonTo(nextRightPosition, control.position)) {
|
|
10490
|
+
this.pointControls.moveRight(rightDistance);
|
|
10491
|
+
} else this.velocity.x = 0;
|
|
10492
|
+
|
|
10493
|
+
const nextForwardPosition = control.position.clone();
|
|
10494
|
+
// moveForward 的正距离沿相机前方移动,预判位置必须与实际位移方向一致。
|
|
10495
|
+
nextForwardPosition.x -= Math.sin(yaw) * forwardDistance;
|
|
10496
|
+
nextForwardPosition.z -= Math.cos(yaw) * forwardDistance;
|
|
10497
|
+
if (this.canMoveFirstPersonTo(nextForwardPosition, control.position)) {
|
|
10498
|
+
this.pointControls.moveForward(forwardDistance);
|
|
10499
|
+
} else this.velocity.z = 0;
|
|
9397
10500
|
control.position.y += this.velocity.y * delta;
|
|
9398
10501
|
// 保证控制器的y轴在平面上
|
|
9399
10502
|
if (control.position.y < FIRST_PERSON_GROUND_HEIGHT) {
|
|
@@ -9418,6 +10521,59 @@ export default {
|
|
|
9418
10521
|
}
|
|
9419
10522
|
}
|
|
9420
10523
|
},
|
|
10524
|
+
canMoveFirstPersonTo(position, currentPosition) {
|
|
10525
|
+
if (!this.firstPersonCollisionEnabled) return true;
|
|
10526
|
+
if (!this._octree && this._boxIndex && this._boxIndex.size) this.buildOctreeFromBoxIndex();
|
|
10527
|
+
if (!this._octree) return true;
|
|
10528
|
+
const radius = Math.max((this.firstPersonActorHeight || 1700) * 0.16, 20);
|
|
10529
|
+
const clearance = Math.max((this.firstPersonActorHeight || 1700) * 0.015, 4);
|
|
10530
|
+
const height = this.firstPersonActorHeight || 1700;
|
|
10531
|
+
const collisionRadius = radius + clearance;
|
|
10532
|
+
const makeActorBox = actorPosition =>
|
|
10533
|
+
new this.THREE.Box3(
|
|
10534
|
+
new this.THREE.Vector3(
|
|
10535
|
+
actorPosition.x - collisionRadius,
|
|
10536
|
+
actorPosition.y - height * 0.9,
|
|
10537
|
+
actorPosition.z - collisionRadius
|
|
10538
|
+
),
|
|
10539
|
+
new this.THREE.Vector3(
|
|
10540
|
+
actorPosition.x + collisionRadius,
|
|
10541
|
+
actorPosition.y - height * 0.1,
|
|
10542
|
+
actorPosition.z + collisionRadius
|
|
10543
|
+
)
|
|
10544
|
+
);
|
|
10545
|
+
const start = currentPosition || position;
|
|
10546
|
+
const startActorBox = makeActorBox(start);
|
|
10547
|
+
const startActorObb = new OBB().fromBox3(startActorBox);
|
|
10548
|
+
const distance = Math.hypot(position.x - start.x, position.z - start.z);
|
|
10549
|
+
const sampleStep = Math.max(radius * 0.35, 5);
|
|
10550
|
+
const sampleCount = Math.max(1, Math.ceil(distance / sampleStep));
|
|
10551
|
+
|
|
10552
|
+
for (let sample = 1; sample <= sampleCount; sample++) {
|
|
10553
|
+
const progress = sample / sampleCount;
|
|
10554
|
+
const samplePosition = new this.THREE.Vector3(
|
|
10555
|
+
start.x + (position.x - start.x) * progress,
|
|
10556
|
+
position.y,
|
|
10557
|
+
start.z + (position.z - start.z) * progress
|
|
10558
|
+
);
|
|
10559
|
+
const actorBox = makeActorBox(samplePosition);
|
|
10560
|
+
const candidates = this._queryOctreeByBox(actorBox);
|
|
10561
|
+
const actorObb = new OBB().fromBox3(actorBox);
|
|
10562
|
+
const blocked = candidates.some(item => {
|
|
10563
|
+
const obstacleBox = item && item.box;
|
|
10564
|
+
if (!this._isCollisionBoxAvailable(obstacleBox)) return false;
|
|
10565
|
+
if (!obstacleBox.intersectsBox(actorBox)) return false;
|
|
10566
|
+
return this._getCollisionObbs(obstacleBox).some(obstacleObb => {
|
|
10567
|
+
const penetration = this._getObbMinPenetration(actorObb, obstacleObb);
|
|
10568
|
+
const initialPenetration = this._getObbMinPenetration(startActorObb, obstacleObb);
|
|
10569
|
+
if (initialPenetration <= 0) return penetration > 0;
|
|
10570
|
+
return penetration > initialPenetration + 0.1;
|
|
10571
|
+
});
|
|
10572
|
+
});
|
|
10573
|
+
if (blocked) return false;
|
|
10574
|
+
}
|
|
10575
|
+
return true;
|
|
10576
|
+
},
|
|
9421
10577
|
// 键盘监听事件
|
|
9422
10578
|
onKeyDown(event) {
|
|
9423
10579
|
if (!event.keyCode) return;
|
|
@@ -9918,6 +11074,8 @@ export default {
|
|
|
9918
11074
|
// 计算相机近裁面到包围盒当前面的距离
|
|
9919
11075
|
this.cameraTrack();
|
|
9920
11076
|
this.firstPerspective();
|
|
11077
|
+
this.updateThirdPersonRoaming(delta);
|
|
11078
|
+
this.syncOrientationAxis();
|
|
9921
11079
|
// 相机姿态(含漫游/第一人称写入)确定后,再刷新远裁剪面
|
|
9922
11080
|
this.syncCameraProjection();
|
|
9923
11081
|
if (this.scenePass) {
|
|
@@ -9966,6 +11124,7 @@ export default {
|
|
|
9966
11124
|
// interactionFrameCount = 0;
|
|
9967
11125
|
// }
|
|
9968
11126
|
}
|
|
11127
|
+
this.renderOrientationAxis();
|
|
9969
11128
|
|
|
9970
11129
|
// 3. 按频率输出(避免每帧都log)
|
|
9971
11130
|
// if (frameCounter++ % LOG_INTERVAL === 0) {
|
|
@@ -10897,6 +12056,98 @@ export default {
|
|
|
10897
12056
|
position: relative;
|
|
10898
12057
|
cursor: pointer;
|
|
10899
12058
|
}
|
|
12059
|
+
|
|
12060
|
+
::v-deep .roaming-settings-dialog .el-dialog__header {
|
|
12061
|
+
padding: 22px 32px 16px;
|
|
12062
|
+
border-bottom: 1px solid #ebeef5;
|
|
12063
|
+
}
|
|
12064
|
+
|
|
12065
|
+
::v-deep .roaming-settings-dialog .el-dialog__title {
|
|
12066
|
+
color: #303133;
|
|
12067
|
+
font-size: 18px;
|
|
12068
|
+
font-weight: 600;
|
|
12069
|
+
}
|
|
12070
|
+
|
|
12071
|
+
::v-deep .roaming-settings-dialog .el-dialog__body {
|
|
12072
|
+
padding: 28px 32px 8px;
|
|
12073
|
+
}
|
|
12074
|
+
|
|
12075
|
+
::v-deep .roaming-settings-dialog .el-dialog__footer {
|
|
12076
|
+
padding: 14px 32px 24px;
|
|
12077
|
+
border-top: 1px solid #ebeef5;
|
|
12078
|
+
}
|
|
12079
|
+
|
|
12080
|
+
.roaming-settings-form {
|
|
12081
|
+
::v-deep .el-form-item__label {
|
|
12082
|
+
color: #303133;
|
|
12083
|
+
font-size: 14px;
|
|
12084
|
+
font-weight: 400;
|
|
12085
|
+
}
|
|
12086
|
+
|
|
12087
|
+
.roaming-settings-perspective {
|
|
12088
|
+
display: flex;
|
|
12089
|
+
align-items: center;
|
|
12090
|
+
min-height: 32px;
|
|
12091
|
+
gap: 72px;
|
|
12092
|
+
|
|
12093
|
+
::v-deep .el-radio {
|
|
12094
|
+
margin-right: 0;
|
|
12095
|
+
color: #303133;
|
|
12096
|
+
font-size: 14px;
|
|
12097
|
+
}
|
|
12098
|
+
|
|
12099
|
+
::v-deep .el-radio__label {
|
|
12100
|
+
padding-left: 8px;
|
|
12101
|
+
font-size: 14px;
|
|
12102
|
+
}
|
|
12103
|
+
}
|
|
12104
|
+
|
|
12105
|
+
.roaming-settings-section-title {
|
|
12106
|
+
margin: 8px 0 20px;
|
|
12107
|
+
color: #303133;
|
|
12108
|
+
font-size: 15px;
|
|
12109
|
+
font-weight: 600;
|
|
12110
|
+
}
|
|
12111
|
+
|
|
12112
|
+
.roaming-settings-height,
|
|
12113
|
+
.roaming-settings-control {
|
|
12114
|
+
width: 280px;
|
|
12115
|
+
}
|
|
12116
|
+
|
|
12117
|
+
.el-slider {
|
|
12118
|
+
width: 330px;
|
|
12119
|
+
padding: 0 8px 0 0;
|
|
12120
|
+
}
|
|
12121
|
+
|
|
12122
|
+
.el-form-item {
|
|
12123
|
+
margin-bottom: 22px;
|
|
12124
|
+
}
|
|
12125
|
+
}
|
|
12126
|
+
|
|
12127
|
+
::v-deep .roaming-settings-dialog {
|
|
12128
|
+
max-width: calc(100vw - 32px);
|
|
12129
|
+
}
|
|
12130
|
+
|
|
12131
|
+
::v-deep .roaming-settings-dialog .el-dialog__close {
|
|
12132
|
+
font-size: 18px;
|
|
12133
|
+
}
|
|
12134
|
+
|
|
12135
|
+
::v-deep .roaming-settings-dialog .el-dialog__body {
|
|
12136
|
+
padding: 10px 20px;
|
|
12137
|
+
}
|
|
12138
|
+
|
|
12139
|
+
.roaming-settings-footer {
|
|
12140
|
+
display: flex;
|
|
12141
|
+
justify-content: flex-end;
|
|
12142
|
+
gap: 8px;
|
|
12143
|
+
|
|
12144
|
+
::v-deep .el-button {
|
|
12145
|
+
min-width: 76px;
|
|
12146
|
+
height: 34px;
|
|
12147
|
+
font-size: 14px;
|
|
12148
|
+
}
|
|
12149
|
+
}
|
|
12150
|
+
|
|
10900
12151
|
::v-deep .tips-label {
|
|
10901
12152
|
width: 60px;
|
|
10902
12153
|
color: #000;
|
|
@@ -11086,9 +12337,16 @@ export default {
|
|
|
11086
12337
|
}
|
|
11087
12338
|
}
|
|
11088
12339
|
</style>
|
|
12340
|
+
|
|
12341
|
+
<style lang="scss">
|
|
12342
|
+
/* el-dialog 开启 append-to-body 后挂载到 body,使用非 scoped 选择器确保样式生效。 */
|
|
12343
|
+
.roaming-settings-dialog .el-dialog__body {
|
|
12344
|
+
padding: 10px 20px;
|
|
12345
|
+
}
|
|
12346
|
+
</style>
|
|
11089
12347
|
<style>
|
|
11090
12348
|
/* 自定义lil-gui样式 - 浅色主题 */
|
|
11091
|
-
.lil-
|
|
12349
|
+
.lil-gui {
|
|
11092
12350
|
background: rgba(255, 255, 255, 0.95) !important;
|
|
11093
12351
|
border: 1px solid #e0e0e0 !important;
|
|
11094
12352
|
border-radius: 8px !important;
|
|
@@ -11097,7 +12355,7 @@ export default {
|
|
|
11097
12355
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif !important;
|
|
11098
12356
|
}
|
|
11099
12357
|
|
|
11100
|
-
.lil-
|
|
12358
|
+
.lil-gui .title {
|
|
11101
12359
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%) !important;
|
|
11102
12360
|
color: #495057 !important;
|
|
11103
12361
|
border-bottom: 1px solid #dee2e6 !important;
|
|
@@ -11106,50 +12364,70 @@ export default {
|
|
|
11106
12364
|
border-radius: 8px 8px 0 0 !important;
|
|
11107
12365
|
}
|
|
11108
12366
|
|
|
11109
|
-
.lil-
|
|
12367
|
+
.lil-gui .controller {
|
|
11110
12368
|
border-bottom: 1px solid #f1f3f4 !important;
|
|
11111
12369
|
background: transparent !important;
|
|
11112
12370
|
}
|
|
11113
12371
|
|
|
11114
|
-
.lil-
|
|
12372
|
+
.lil-gui .controller:last-child {
|
|
11115
12373
|
border-bottom: none !important;
|
|
11116
12374
|
}
|
|
11117
12375
|
|
|
11118
|
-
.lil-
|
|
12376
|
+
.lil-gui .controller .name {
|
|
11119
12377
|
color: #495057 !important;
|
|
11120
12378
|
font-weight: 500 !important;
|
|
11121
12379
|
font-size: 12px !important;
|
|
11122
12380
|
}
|
|
11123
12381
|
|
|
11124
|
-
.lil-
|
|
12382
|
+
.lil-gui .controller .widget {
|
|
11125
12383
|
background: #f8f9fa !important;
|
|
11126
12384
|
border: 1px solid #ced4da !important;
|
|
11127
12385
|
border-radius: 4px !important;
|
|
11128
12386
|
color: #495057 !important;
|
|
11129
12387
|
}
|
|
11130
12388
|
|
|
11131
|
-
.lil-
|
|
12389
|
+
.lil-gui .controller .widget:hover {
|
|
11132
12390
|
border-color: #80bdff !important;
|
|
11133
12391
|
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25) !important;
|
|
11134
12392
|
}
|
|
11135
12393
|
|
|
11136
|
-
.lil-
|
|
12394
|
+
.lil-gui .controller .widget:focus {
|
|
11137
12395
|
border-color: #80bdff !important;
|
|
11138
12396
|
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25) !important;
|
|
11139
12397
|
outline: none !important;
|
|
11140
12398
|
}
|
|
11141
12399
|
|
|
11142
|
-
.lil-
|
|
12400
|
+
.lil-gui .global-clipping-wireframe-toggle label {
|
|
12401
|
+
display: flex !important;
|
|
12402
|
+
align-items: center !important;
|
|
12403
|
+
justify-content: flex-start !important;
|
|
12404
|
+
background: transparent !important;
|
|
12405
|
+
border: 0 !important;
|
|
12406
|
+
box-shadow: none !important;
|
|
12407
|
+
}
|
|
12408
|
+
|
|
12409
|
+
.lil-gui .global-clipping-wireframe-toggle input[type='checkbox'] {
|
|
12410
|
+
width: 16px !important;
|
|
12411
|
+
height: 16px !important;
|
|
12412
|
+
margin: 0 8px 0 0 !important;
|
|
12413
|
+
padding: 0 !important;
|
|
12414
|
+
color: #495057 !important;
|
|
12415
|
+
border: 1px solid !important;
|
|
12416
|
+
accent-color: #1684d8;
|
|
12417
|
+
cursor: pointer;
|
|
12418
|
+
}
|
|
12419
|
+
|
|
12420
|
+
.lil-gui .controller input[type='range'] {
|
|
11143
12421
|
background: #e9ecef !important;
|
|
11144
12422
|
height: 4px !important;
|
|
11145
12423
|
-webkit-appearance: none !important;
|
|
11146
12424
|
appearance: none !important;
|
|
11147
12425
|
border-radius: 2px !important;
|
|
11148
12426
|
}
|
|
11149
|
-
.lil-
|
|
12427
|
+
.lil-gui .controller.number .fill {
|
|
11150
12428
|
border-right: solid #008de9;
|
|
11151
12429
|
}
|
|
11152
|
-
.lil-
|
|
12430
|
+
.lil-gui .controller input[type='range']::-webkit-slider-thumb {
|
|
11153
12431
|
background: #007bff !important;
|
|
11154
12432
|
border: 2px solid #ffffff !important;
|
|
11155
12433
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) !important;
|
|
@@ -11161,7 +12439,7 @@ export default {
|
|
|
11161
12439
|
cursor: pointer !important;
|
|
11162
12440
|
}
|
|
11163
12441
|
|
|
11164
|
-
.lil-
|
|
12442
|
+
.lil-gui .controller input[type='range']::-moz-range-thumb {
|
|
11165
12443
|
background: #007bff !important;
|
|
11166
12444
|
border: 2px solid #ffffff !important;
|
|
11167
12445
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) !important;
|
|
@@ -11171,23 +12449,23 @@ export default {
|
|
|
11171
12449
|
cursor: pointer !important;
|
|
11172
12450
|
}
|
|
11173
12451
|
|
|
11174
|
-
.lil-
|
|
12452
|
+
.lil-gui .controller .option {
|
|
11175
12453
|
background: #ffffff !important;
|
|
11176
12454
|
color: #495057 !important;
|
|
11177
12455
|
border-bottom: 1px solid #f1f3f4 !important;
|
|
11178
12456
|
}
|
|
11179
12457
|
|
|
11180
|
-
.lil-
|
|
12458
|
+
.lil-gui .controller .option:hover {
|
|
11181
12459
|
background: #f8f9fa !important;
|
|
11182
12460
|
}
|
|
11183
12461
|
|
|
11184
|
-
.lil-
|
|
12462
|
+
.lil-gui .controller .option:last-child {
|
|
11185
12463
|
border-bottom: none !important;
|
|
11186
12464
|
}
|
|
11187
|
-
.lil-
|
|
12465
|
+
.lil-gui input:active {
|
|
11188
12466
|
background: #e6eff4;
|
|
11189
12467
|
}
|
|
11190
|
-
.lil-
|
|
12468
|
+
.lil-gui .controller button {
|
|
11191
12469
|
background: linear-gradient(135deg, #007bff 0%, #0056b3 100%) !important;
|
|
11192
12470
|
color: #ffffff !important;
|
|
11193
12471
|
border: none !important;
|
|
@@ -11196,51 +12474,51 @@ export default {
|
|
|
11196
12474
|
transition: all 0.2s ease !important;
|
|
11197
12475
|
}
|
|
11198
12476
|
|
|
11199
|
-
.lil-
|
|
12477
|
+
.lil-gui .controller button:hover {
|
|
11200
12478
|
background: linear-gradient(135deg, #0056b3 0%, #004085 100%) !important;
|
|
11201
12479
|
transform: translateY(-1px) !important;
|
|
11202
12480
|
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3) !important;
|
|
11203
12481
|
}
|
|
11204
12482
|
|
|
11205
|
-
.lil-
|
|
12483
|
+
.lil-gui .controller .color {
|
|
11206
12484
|
border: 2px solid #ffffff !important;
|
|
11207
12485
|
border-radius: 4px !important;
|
|
11208
12486
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
|
|
11209
12487
|
}
|
|
11210
|
-
.lil-
|
|
12488
|
+
.lil-gui .controller.number .slider {
|
|
11211
12489
|
background-color: #e6eff4;
|
|
11212
12490
|
}
|
|
11213
|
-
.lil-
|
|
12491
|
+
.lil-gui .controller.number .slider:hover {
|
|
11214
12492
|
background-color: #e6eff4;
|
|
11215
12493
|
}
|
|
11216
|
-
.lil-
|
|
12494
|
+
.lil-gui input:hover {
|
|
11217
12495
|
background: #e6eff4;
|
|
11218
12496
|
}
|
|
11219
|
-
.lil-
|
|
11220
|
-
.lil-
|
|
11221
|
-
.lil-
|
|
12497
|
+
.lil-gui input[type='number']:focus,
|
|
12498
|
+
.lil-gui input[type='text']:focus,
|
|
12499
|
+
.lil-gui input {
|
|
11222
12500
|
background: #e6eff4;
|
|
11223
12501
|
}
|
|
11224
|
-
.lil-
|
|
12502
|
+
.lil-gui .controller > .name {
|
|
11225
12503
|
min-width: 25px;
|
|
11226
12504
|
}
|
|
11227
|
-
.lil-
|
|
12505
|
+
.lil-gui .controller.number input {
|
|
11228
12506
|
color: #2e3136;
|
|
11229
12507
|
}
|
|
11230
|
-
.lil-
|
|
12508
|
+
.lil-gui .controller.number .slider:active {
|
|
11231
12509
|
background-color: #e6eff4;
|
|
11232
12510
|
}
|
|
11233
|
-
.lil-
|
|
12511
|
+
.lil-gui .folder > .title {
|
|
11234
12512
|
background: linear-gradient(135deg, #f1f3f4 0%, #e9ecef 100%) !important;
|
|
11235
12513
|
color: #495057 !important;
|
|
11236
12514
|
border-bottom: 1px solid #dee2e6 !important;
|
|
11237
12515
|
}
|
|
11238
12516
|
|
|
11239
|
-
.lil-
|
|
12517
|
+
.lil-gui .folder > .title:before {
|
|
11240
12518
|
color: #6c757d !important;
|
|
11241
12519
|
}
|
|
11242
12520
|
|
|
11243
|
-
/* .lil-
|
|
12521
|
+
/* .lil-gui .folder.closed > .children {
|
|
11244
12522
|
display: none !important;
|
|
11245
12523
|
} */
|
|
11246
12524
|
.lil-gui button {
|