fl-web-component 2.0.0-beta.4 → 2.0.0-beta.6

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.
@@ -1,1679 +0,0 @@
1
- <template>
2
- <div id="fl-model"></div>
3
- </template>
4
- <script>
5
- var [
6
- renderer,
7
- scene,
8
- camera,
9
- cameraControls,
10
- instructions,
11
- raycaster,
12
- mouse,
13
- labelRenderer,
14
- lineTexture,
15
- curve,
16
- downRaycaster,
17
- velocity,
18
- direction,
19
- clock,
20
- pointControls,
21
- threeMeasure,
22
- modelGroup,
23
- gui,
24
- animateId,
25
- scenePass,
26
- outlineComposer,
27
- renderTarget,
28
- sceneClock,
29
- mat4,
30
- ] = (function* (v) {
31
- while (true) yield v;
32
- })(null);
33
-
34
- var [lastTime, firstTime, fpsClock, timeStamp, progress] = (function* (v) {
35
- while (true) yield v;
36
- })(0);
37
- var singleFrameTime = 1 / 30;
38
-
39
- // 双缓冲渲染
40
- var postScene, postCamera, postTarget
41
-
42
- var [
43
- roaming,
44
- firstPerSign,
45
- canJump,
46
- moveForward,
47
- moveBackward,
48
- moveLeft,
49
- moveRight,
50
- measureFlag,
51
- renderEnabled,
52
- sceneChangeFlag,
53
- ] = (function* (v) {
54
- while (true) yield v;
55
- })(false);
56
-
57
- var [spaceUp] = (function* (v) {
58
- while (true) yield v;
59
- })(true);
60
-
61
- var clippingMesh = [],
62
- modelActive = [],
63
- modelActions = [];
64
- var removeSpeed = 200,
65
- upSpeed = 200; //控制器移动速度 , //控制跳起时的速度
66
- var roamConfig = {
67
- loop: false,
68
- speed: 0, // 最大值为3
69
- name: '',
70
- };
71
- var guiParams = {
72
- x轴: 0,
73
- y轴: 0,
74
- z轴: 0,
75
- };
76
- // 绘制对象映射实例表
77
- import CameraControls from 'camera-controls';
78
- import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
79
- import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer';
80
- import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer';
81
- import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls.js';
82
- import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
83
- import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js';
84
- import MeasureDistance from '@/utils/threejs/measure-distance.js';
85
- import MeasureArea from '@/utils/threejs/measure-area.js';
86
- import MeasureAngle from '@/utils/threejs/measure-angle.js';
87
- import { parseData } from '@/utils/flgltf-parser';
88
- import { handleInstancedMeshModel } from '@/utils/instance-parser';
89
- import { RainShader } from '@/utils/threejs/rain-shader.js';
90
- import { SnowShader } from '@/utils/threejs/snow-shader.js';
91
- import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
92
- import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js';
93
- import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';
94
- export default {
95
- name: 'FlModel',
96
- props: {
97
- data: {
98
- type: Object,
99
- default() {
100
- return {};
101
- },
102
- },
103
- },
104
- data() {
105
- return {};
106
- },
107
- created() {
108
- CameraControls.install({ THREE: this.THREE });
109
- mat4 = new this.THREE.Matrix4();
110
- mat4.makeRotationX(-Math.PI / 2);
111
- fpsClock = new this.THREE.Clock();
112
- raycaster = new this.THREE.Raycaster();
113
- sceneClock = new this.THREE.Clock();
114
- mouse = new this.THREE.Vector2();
115
- renderTarget = new this.THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
116
- minFilter: this.THREE.LinearFilter,
117
- magFilter: this.THREE.LinearFilter,
118
- format: this.THREE.RGBAFormat,
119
- stencilBuffer: true,
120
- });
121
- },
122
- mounted() {
123
- instructions = document.getElementById('fl-model');
124
- this.initRender();
125
- this.initScene();
126
- this.initCamera();
127
- this.initControl();
128
- this.initLight();
129
- this.initLabelRender();
130
- this.exportParmas();
131
- // 判断是设备是手机还是电脑
132
- let isMobileDevice = this.isMobileDevice();
133
- if (isMobileDevice) {
134
- renderer.domElement.addEventListener('pointerup', this.mouseClick, false);
135
- renderer.domElement.addEventListener('pointerdown', this.mouseDown, false);
136
- } else {
137
- renderer.domElement.addEventListener('mouseup', this.mouseClick, false);
138
- renderer.domElement.addEventListener('mousedown', this.mouseDown, false);
139
- }
140
- // 新增鼠标滑过事件
141
- cameraControls.addEventListener('wake', () => {
142
- renderEnabled = true;
143
- });
144
- cameraControls.addEventListener('sleep', () => {
145
- renderEnabled = false;
146
- scene.renderStage.init();
147
- });
148
- this.animate();
149
- },
150
- methods: {
151
- // 判断是设备是手机还是电脑
152
- isMobileDevice() {
153
- const userAgent = navigator.userAgent || navigator.vendor || window.opera;
154
- if (/windows phone/i.test(userAgent)) {
155
- return true;
156
- }
157
- if (/android/i.test(userAgent)) {
158
- return true;
159
- }
160
- if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) {
161
- return true;
162
- }
163
- return false;
164
- },
165
- initRender() {
166
- renderer = new this.THREE.WebGLRenderer({
167
- antialias: true,
168
- alpha: true,
169
- logarithmicDepthBuffer: true,
170
- powerPreference: 'high-performance',
171
- preserveDrawingBuffer: true, //保留图形缓冲区 TODO 临时截图使用
172
- });
173
- renderer.setPixelRatio(window.devicePixelRatio);
174
- const rect = instructions.getBoundingClientRect();
175
- renderer.setSize(rect.width, rect.height);
176
- renderer.domElement.id = 'three-model';
177
- renderer.shadowMap.enabled = true;
178
- // 暂时注释这句 还有worker里面的 这跟渲染出的模型浅或暗有关系
179
- renderer.outputEncoding = this.THREE.sRGBEncoding;
180
- instructions.appendChild(renderer.domElement);
181
- renderer.setClearAlpha(0);
182
-
183
- // 与校审截图功能冲突,暂时先注释掉
184
- // -----------
185
- // renderer.autoClear = false;
186
- // renderer.autoClearColor = false;
187
- // renderer.autoClearDepth = false;
188
- // renderer.autoClearStencil = false;
189
- // -----------
190
- },
191
- initSenceRenderTarget(width, height) {
192
- postTarget = new this.THREE.WebGLRenderTarget(width, height, 1);
193
- postScene.background = postTarget.texture; // new this.THREE.Color("rgb(0, 255, 0)")//
194
- // postScene.background = new this.THREE.TextureLoader().load( '/src/views/model-three/land_ocean_ice_cloud_2048.jpg', null, null, ()=>{console.log("loaderror")} );
195
- },
196
- initScene() {
197
- modelGroup = new this.THREE.Group();
198
- scene = new this.THREE.Scene();
199
- scene.renderStage = new this.THREE.RenderStage();
200
- scene.userData.recordEntity = [];
201
- postScene = new this.THREE.Scene();
202
- },
203
- initCamera() {
204
- camera = new this.THREE.PerspectiveCamera(
205
- 45,
206
- window.innerWidth / window.innerHeight,
207
- 0.1,
208
- 1000000000
209
- );
210
- camera.position.set(0, 100, 150);
211
- this.initSenceRenderTarget(
212
- window.innerWidth * window.devicePixelRatio * 2,
213
- window.innerHeight * window.devicePixelRatio * 2
214
- );
215
- postCamera = new this.THREE.OrthographicCamera(0.5, -0.5, 0.5, -0.5, 0.01, 1000); //window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 0.1, 1000)
216
- postCamera.position.set(0, 0, 150);
217
- postCamera.lookAt(scene.position);
218
- },
219
- initControl() {
220
- // 初始化控件
221
- cameraControls = new CameraControls(camera, renderer.domElement);
222
- cameraControls.dollyToCursor = true;
223
- cameraControls.smoothTime = 0.1;
224
- cameraControls.draggingSmoothTime = 0.05;
225
- cameraControls.truckSpeed = 2.0;
226
- cameraControls.infinityDolly = true;
227
- cameraControls.minDistance = 4;
228
- },
229
- // 初始化光源
230
- initLight() {
231
- const pmremGenerator = new this.THREE.PMREMGenerator(renderer);
232
- scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
233
- },
234
- // 初始化文字画布
235
- initLabelRender() {
236
- labelRenderer = new CSS2DRenderer();
237
- const rect = instructions.getBoundingClientRect();
238
- labelRenderer.setSize(rect.width, rect.height);
239
- labelRenderer.domElement.style.position = 'absolute';
240
-
241
- labelRenderer.domElement.style.top = `${rect.top}px`;
242
- labelRenderer.domElement.style.pointerEvents = 'none';
243
- instructions.appendChild(labelRenderer.domElement);
244
- },
245
- // 根据模型数据绘制模型实体 业务平台可调用此方法加载模型
246
- /*
247
- 参数:data 模型数据
248
- color: '' 初始化模型的颜色 在业务方 有这个需求
249
- meshNameConfig: {}
250
- */
251
- async drawModel(data, color = '', meshNameConfig = {}) {
252
- if (Object.keys(data).length === 0) {
253
- return;
254
- }
255
- const { instances, drawObjs } = parseData(data);
256
- if (instances.length > 0) {
257
- await handleInstancedMeshModel(
258
- modelGroup,
259
- instances,
260
- drawObjs,
261
- '',
262
- scene,
263
- color,
264
- meshNameConfig
265
- );
266
- let modelBox3 = new this.THREE.Box3();
267
- modelBox3.expandByObject(modelGroup);
268
- let modelWorldPs = new this.THREE.Vector3()
269
- .addVectors(modelBox3.max, modelBox3.min)
270
- .multiplyScalar(0.5);
271
- scene.add(modelGroup);
272
- // 适配客户端的坐标系,threejs坐标需绕x轴旋转90度
273
- modelGroup.applyMatrix4(mat4);
274
- modelGroup.updateMatrixWorld();
275
- modelGroup.traverse(child => {
276
- if (child.isMesh) {
277
- const json = this.getMeshCenterAndVolume(child);
278
- let meshBox3 = new this.THREE.Box3();
279
- meshBox3.setFromObject(child);
280
- // 获取每个mesh的中心点,爆炸方向为爆炸中心点指向mesh中心点
281
- let worldPs = new this.THREE.Vector3()
282
- .addVectors(meshBox3.max, meshBox3.min)
283
- .multiplyScalar(0.5);
284
- if (isNaN(worldPs.x)) return;
285
- // 计算爆炸方向
286
- child.worldDir = new this.THREE.Vector3()
287
- .subVectors(worldPs, modelWorldPs)
288
- .normalize();
289
- // 保存初始坐标
290
- child.userData.center = json.center;
291
- child.userData.worldPs = worldPs;
292
- child.userData.oldPs = child.getWorldPosition(new this.THREE.Vector3());
293
- child.userData.box = json.box;
294
- child.userData.position = new this.THREE.Vector3().copy(child.position);
295
- child.userData.translate = {
296
- x: 0,
297
- y: 0,
298
- z: 0,
299
- };
300
- child.userData.rotate = {
301
- x: 0,
302
- y: 0,
303
- z: 0,
304
- };
305
- child.userData.modelWorldPs = modelWorldPs;
306
- }
307
- });
308
- // cameraControls.fitToSphere(scene, true); // TODO 待处理,先用 setModelCenter 进行定位
309
- this.setModelCenter(modelGroup);
310
- this.$emit('modelLoaded');
311
- renderEnabled = true;
312
- sceneChangeFlag = true;
313
- // cameraControls.saveState();
314
- }
315
- },
316
- // 获取mesh的中心点
317
- getMeshCenterAndVolume(mesh) {
318
- const box = new this.THREE.Box3().setFromObject(mesh);
319
- const volume = box.getSize(new this.THREE.Vector3());
320
- const geometry = mesh.geometry;
321
- geometry.computeBoundingBox();
322
- const center = geometry.boundingBox.getCenter(new this.THREE.Vector3());
323
- return {
324
- center: center,
325
- box: volume,
326
- };
327
- },
328
- mouseDown(event) {
329
- const intersects = this.getRaycasterObjects(event);
330
- firstTime = new Date().getTime();
331
- let params = {
332
- event,
333
- firstTime,
334
- };
335
- intersects.length &&
336
- (params.v3Position = {
337
- x: intersects[0].point.x,
338
- y: intersects[0].point.y,
339
- z: intersects[0].point.z,
340
- });
341
- this.$emit('leftMouseDown', params);
342
- },
343
- getRaycasterObjects(event) {
344
- // 获取元素在页面中的偏移位置
345
- const rect = renderer.domElement.getBoundingClientRect();
346
- const x = event.clientX - rect.left;
347
- const y = event.clientY - rect.top;
348
-
349
- // mouse.x = (event.clientX / instructions.offsetWidth) * 2 - 1;
350
- // mouse.y = -(event.clientY / instructions.offsetHeight) * 2 + 1;
351
- mouse.x = (x / rect.width) * 2 - 1;
352
- mouse.y = -(y / rect.height) * 2 + 1;
353
- raycaster.setFromCamera(mouse, camera);
354
- return raycaster.intersectObjects(scene.children, true);
355
- },
356
- mouseClick(event) {
357
- // 在测量模式下,不进行事件暴露
358
- if (!measureFlag) {
359
- lastTime = new Date().getTime();
360
- if (lastTime - firstTime < 300) {
361
- const intersects = this.getRaycasterObjects(event);
362
- let params = {};
363
- let cameraData = {
364
- position: {
365
- x: cameraControls.camera.position.x,
366
- y: cameraControls.camera.position.y,
367
- z: cameraControls.camera.position.z,
368
- },
369
- lookAt: {
370
- heading: cameraControls._target.x,
371
- pitch: cameraControls._target.y,
372
- roll: cameraControls._target.z,
373
- },
374
- };
375
- if (intersects.length > 0) {
376
- params = {
377
- objects: [intersects[0].object],
378
- mousePosition: { x: event.clientX, y: event.clientY },
379
- camera: cameraData,
380
- v3Position: {
381
- x: intersects[0].point.x,
382
- y: intersects[0].point.y,
383
- z: intersects[0].point.z,
384
- },
385
- };
386
- } else {
387
- params = {
388
- objects: [],
389
- mousePosition: { x: event.clientX, y: event.clientY },
390
- camera: cameraData,
391
- v3Position: {
392
- x: -1,
393
- y: -1,
394
- z: -1,
395
- },
396
- };
397
- }
398
- if (event.button === 0) {
399
- this.$emit('leftClick', params);
400
- } else if (event.button === 2) {
401
- this.$emit('rightClick', params);
402
- }
403
- }
404
- }
405
- },
406
- // 窗口发生改变时, 更新渲染器与相机的大小
407
- resize(width, height) {
408
- if (camera) {
409
- camera.aspect = width / height;
410
- camera.updateProjectionMatrix();
411
- renderer.setSize(width, height, true);
412
- labelRenderer.setSize(width, height);
413
- // this.timeRender()
414
- // 这里也要更新测量
415
- if (threeMeasure) {
416
- threeMeasure.updateParams(width, height);
417
- }
418
- }
419
- },
420
- setModelCenter(mesh) {
421
- const box3 = new this.THREE.Box3();
422
- mesh.traverseVisible(function (object) {
423
- // 3. 只处理有几何体的网格 (Mesh)
424
- if (object.isMesh) {
425
- // 4. 使用 expandByObject 扩展包围盒
426
- // 这个方法会计算 object 的世界坐标包围盒,并将其合并到 correctBoundingBox 中
427
- box3.expandByObject(object);
428
- }
429
- });
430
- // box3.setFromObject(mesh);
431
- const center = new this.THREE.Vector3();
432
- box3.getCenter(center);
433
- const size = box3.getSize(new this.THREE.Vector3());
434
- camera.position.set(center.x, center.y + size.y, center.z + size.z / 2);
435
- camera.lookAt(new this.THREE.Vector3(center.x, center.y, center.z));
436
- cameraControls.setLookAt(
437
- center.x,
438
- center.y + size.y,
439
- center.z + size.z / 2,
440
- center.x,
441
- center.y,
442
- center.z,
443
- true
444
- );
445
- cameraControls.update(0);
446
- camera.updateProjectionMatrix();
447
- cameraControls.saveState();
448
- },
449
- // 修改指定模型实体属性
450
- /**
451
- * 参数内容{
452
- * list: [{
453
- * name: '', // 实体的name值'
454
- * attr: {
455
- * 'color/visible/opacity': '颜色值/(true / false)/(0-1)'
456
- * }, 需要修改属性的列表
457
- * }] // 需要更改实体属性的数据
458
- * It can be a CSS-style string. For example:
459
- * 'rgb(250, 0,0)'
460
- * 'rgb(100%,0%,0%)'
461
- * 'hsl(0, 100%, 50%)'
462
- * '#ff0000'
463
- * '#f00'
464
- * 'red'
465
- * }
466
- */
467
- updateProperty(list) {
468
- for (let index = 0; index < list.length; index++) {
469
- let ele = list[index];
470
- let targetObj = this.getObjectByName(ele.name);
471
- for (const key in ele.attr) {
472
- switch (key) {
473
- case 'color':
474
- targetObj.forEach(children => {
475
- if (children.isMesh) {
476
- children.setColorAt(
477
- children.userData.instanceIndex,
478
- new this.THREE.Color(ele.attr[key])
479
- );
480
- children.instanceColor.needsUpdate = true;
481
- }
482
- });
483
- break;
484
- case 'visible':
485
- targetObj.forEach(children => {
486
- const index = children.userData.instanceIndex;
487
- if (ele.attr[key]) {
488
- const restoreMatrix = new this.THREE.Matrix4().copy(
489
- children.userData.copyMatrix
490
- );
491
- children.setMatrixAt(index, restoreMatrix);
492
- } else {
493
- const offsetMatrix = new this.THREE.Matrix4()
494
- .copy(children.userData.copyMatrix)
495
- .makeTranslation(9999999, 9999999, 9999999);
496
- children.setMatrixAt(index, offsetMatrix);
497
- }
498
- children.instanceMatrix.needsUpdate = true;
499
- });
500
- break;
501
- case 'opacity':
502
- targetObj.forEach(children => {
503
- if (children.isMesh) {
504
- const opacity = children.geometry.attributes.opacity.array;
505
- opacity[children.userData.instanceIndex] = ele.attr[key];
506
- children.geometry.attributes.opacity.needsUpdate = true;
507
- }
508
- });
509
- break;
510
- case 'position':
511
- targetObj.forEach(children => {
512
- children.position.set(ele.attr[key].x, ele.attr[key].y, ele.attr[key].z);
513
- });
514
- break;
515
- }
516
- }
517
- // obj.material.needsUpdate = true;
518
- }
519
- },
520
- // 恢复模型原来的状态
521
- updateWholeProperty() {
522
- throw new Error('该方法已暂停使用,请使用restore方法。');
523
- // if (scene) {
524
- // scene.traverse(obj => {
525
- // if (obj instanceof this.THREE.Mesh) {
526
- // // 恢复颜色
527
- // obj.setColorAt(obj.userData.instanceIndex, obj.material.userData.nColor)
528
- // obj.instanceColor.needsUpdate = true
529
- // // 修改可见性
530
- // const restoreMatrix = new this.THREE.Matrix4().copy(obj.userData.copyMatrix);
531
- // obj.setMatrixAt(index, restoreMatrix);
532
- // obj.instanceMatrix.needsUpdate = true;
533
- // }
534
- // });
535
- // }
536
- },
537
- // 清除上一次的属性修改操作 为了方便业务平台参数跟updateProperty方法的参数一样
538
- resetProperty(list) {
539
- for (let index = 0; index < list.length; index++) {
540
- let ele = list[index];
541
- let obj = this.getObjectByName(ele.name);
542
- if (obj) {
543
- for (const key in ele.attr) {
544
- switch (key) {
545
- case 'color':
546
- obj.forEach(children => {
547
- if (children.isMesh) {
548
- children.setColorAt(
549
- children.userData.instanceIndex,
550
- children.material.userData.nColor
551
- );
552
- children.instanceColor.needsUpdate = true;
553
- }
554
- });
555
- break;
556
- case 'visible':
557
- obj.forEach(children => {
558
- const index = children.userData.instanceIndex;
559
- const restoreMatrix = new this.THREE.Matrix4().copy(
560
- children.userData.copyMatrix
561
- );
562
- children.setMatrixAt(index, restoreMatrix);
563
- children.instanceMatrix.needsUpdate = true;
564
- });
565
- break;
566
- case 'opacity':
567
- obj.forEach(children => {
568
- if (children.isMesh) {
569
- const opacity = children.geometry.attributes.opacity.array;
570
- opacity[children.userData.instanceIndex] =
571
- children.material.userData.nOpacity;
572
- children.geometry.attributes.opacity.needsUpdate = true;
573
- }
574
- });
575
- }
576
- }
577
- }
578
- }
579
- },
580
- // 定位到模型
581
- locateModel(name) {
582
- let obj = scene.getObjectByName(name);
583
- if (obj) {
584
- // cameraControls.fitToSphere(obj.parent, true); // TODO 待处理,先用 setModelCenter 进行定位
585
- if (obj.isGroup) {
586
- this.setModelCenter(obj);
587
- } else if (obj.isMesh) {
588
- let center = this.getCenter(obj);
589
- let size = this.getSize(obj);
590
- this.locateByCenterBox(center, size);
591
- }
592
- }
593
- },
594
- // 根据自定义参数修改模型
595
- /*
596
- 自定义参数指的是在调用drawModel时配置的meshNameConfig里面的字段来修改某一类模型实体的属性
597
- {
598
- customName: '', 自定义字段名称
599
- customValue: '' 自定义字段的值
600
- attr: {
601
- color: '',
602
- opacity: 0 -1,
603
- visiable: true/false
604
- }
605
- }
606
- */
607
- updatePropertyByCustom(params) {
608
- scene.traverse(child => {
609
- if (child.isMesh && child.userData[params.customName] === params.customValue) {
610
- for (const key in params.attr) {
611
- switch (key) {
612
- case 'color':
613
- child.setColorAt(
614
- obj.userData.instanceIndex,
615
- new this.THREE.Color(params.attr[key])
616
- );
617
- child.instanceColor.needsUpdate = true;
618
- break;
619
- case 'visible':
620
- child.material.visible = params.attr[key];
621
- break;
622
- case 'opacity':
623
- child.material.opacity = params.attr[key];
624
- child.material.transparent = true;
625
- break;
626
- }
627
- child.material.needsUpdate = true;
628
- }
629
- }
630
- });
631
- },
632
- // 相机定位
633
- /*
634
- 定位参数:x: 相机的x位置, y: 相机的y位置, z: 相机的z位置
635
- heading, pitch, roll
636
- 视点定位可直接使用此方法
637
- */
638
- cameraLocation(params) {
639
- cameraControls.setLookAt(
640
- params.x,
641
- params.y,
642
- params.z,
643
- params.heading,
644
- params.pitch,
645
- params.roll,
646
- true
647
- );
648
- cameraControls.update(0);
649
- },
650
- // 使用中心点和实体的长宽高进行定位
651
- /*
652
- center: {x: 0, y: 0, z: 0},
653
- box: {x: 0, y: 0, z: 0}
654
- */
655
- locateByCenterBox(center, size) {
656
- let maxDim = Math.max(size.x, size.y, size.z);
657
- let fov = camera.fov * (Math.PI / 180);
658
- let distance = Math.min(
659
- maxDim * 100, // 最大距离限制
660
- Math.abs((maxDim * 1.0) / Math.sin(fov / 2))
661
- );
662
- let direction = new this.THREE.Vector3(1, 1, 1).normalize();
663
- let p = new this.THREE.Vector3().copy(center).add(direction.multiplyScalar(distance));
664
- let cameraCenter = new this.THREE.Vector3(p.x, p.y, p.z).addScalar(
665
- Math.max(size.x, size.y, size.z)
666
- );
667
- cameraControls.setLookAt(
668
- cameraCenter.x,
669
- cameraCenter.y,
670
- cameraCenter.z,
671
- center.x,
672
- center.y,
673
- center.z,
674
- true
675
- );
676
- },
677
- // 添加广告牌
678
- /*
679
- 参数:
680
- {
681
- billboard: DOM节点
682
- name: 2d广告牌的名字,用来做清除用的
683
- x、y、z为广告牌的位置
684
- }
685
- */
686
- billboard(data) {
687
- const divLabel = new CSS2DObject(data.billboard);
688
- divLabel.name = data.labelClass; // 这个是用来清除广告牌用的
689
- divLabel.position.set(data.x, data.y, data.z);
690
- scene.add(divLabel);
691
- return divLabel;
692
- },
693
- // 通过名字获取实体对象, 返回数组
694
- getObjectByName(name) {
695
- let object = [];
696
- scene.traverse(item => {
697
- if (item.name === name) {
698
- object.push(item);
699
- }
700
- });
701
- return object;
702
- },
703
- // 通过id获取实体对象, 返回查找到的对象
704
- getObjectById(id) {
705
- return scene.getObjectById(id);
706
- },
707
- // 通过id删除对象
708
- removeObjectById(id) {
709
- let array = this.getObjectByName(id);
710
- array.forEach(item => {
711
- if (item.name === id) {
712
- item.material && item.material.dispose();
713
- item.geometry && item.geometry.dispose();
714
- if (item.isMesh) {
715
- item.clear();
716
- }
717
- scene.remove(item);
718
- }
719
- });
720
- },
721
- // 通过名称删除对象
722
- removeObjectByName(name) {
723
- let array = this.getObjectByName(name);
724
- array.forEach(item => {
725
- item.material && item.material.dispose();
726
- item.geometry && item.geometry.dispose();
727
- if (item.isMesh) {
728
- item.clear();
729
- }
730
- scene.remove(item);
731
- });
732
- },
733
- // 删除场景中所有的实体
734
- removeAll() {
735
- return new Promise(resolve => {
736
- if (scene) {
737
- this.removeTraverse();
738
- resolve();
739
- } else {
740
- resolve();
741
- }
742
- });
743
- },
744
- removeTraverse() {
745
- let length = modelGroup.children.length;
746
- if (length > 0) {
747
- let list = modelGroup.children[0];
748
- list.traverse(item => {
749
- if (item.isMesh) {
750
- item.material.dispose();
751
- item.geometry.dispose();
752
- item.clear();
753
- item.material = null;
754
- item.geometry = null;
755
- modelGroup.remove(item);
756
- item = null;
757
- }
758
- });
759
- modelGroup.remove(list);
760
- this.removeTraverse();
761
- } else {
762
- // 在这里清除一些标记之类的
763
- modelGroup.clear();
764
- scene.remove(modelGroup);
765
- renderer.clear();
766
- modelGroup = null;
767
- modelGroup = new this.THREE.Group();
768
- }
769
- },
770
- // 销毁场景 释放内存
771
- destroyScene() {
772
- cancelAnimationFrame(animateId);
773
- if (scene) {
774
- this.removeTraverse();
775
- scene.clear();
776
- scene = null;
777
- }
778
- renderer.forceContextLoss();
779
- renderer.dispose();
780
- camera = null;
781
- cameraControls = null;
782
- renderer.domElement = null;
783
- renderer = null;
784
- },
785
- // 绘制曲线
786
- /*
787
- 参数声明:Object
788
- {
789
- path: [{x:0, y: 0, z: 0}], 坐标点信息
790
- color: '', // 线条的颜色
791
- name: '', // 线条的名字, 用来清除时使用的
792
- }
793
- */
794
- drawCurve(params) {
795
- this.removeObjectByName(params.name);
796
- const route = [];
797
- params.path.forEach(element => {
798
- route.push(new this.THREE.Vector3(element.x, element.y, element.z));
799
- });
800
- if (route.length > 1) {
801
- curve = new this.THREE.CatmullRomCurve3(route);
802
- const geometryLine = new this.THREE.BufferGeometry().setFromPoints(curve.getPoints(5000));
803
- const materialLine = new this.THREE.LineBasicMaterial({
804
- color: params.color,
805
- depthTest: false,
806
- transparent: true,
807
- });
808
- let line = new this.THREE.Line(geometryLine, materialLine);
809
- line.name = params.name;
810
- scene.add(line);
811
- }
812
- },
813
- // 绘制贴图曲线
814
- /*
815
- 参数声明:Object
816
- {
817
- path: [{x:0, y: 0, z: 0}], 坐标点信息
818
- name: '', // 线条的名字, 用来清除时使用的
819
- }
820
- */
821
- drawTextureCurve(params) {
822
- this.removeObjectByName(params.name);
823
- const route = [];
824
- params.path.forEach(element => {
825
- route.push(new this.THREE.Vector3(element.x, element.y, element.z));
826
- });
827
- // 画曲线
828
- if (route.length > 1) {
829
- // 曲线 作为路径
830
- curve = new this.THREE.CatmullRomCurve3(route, false, 'catmullrom', 0);
831
- const geometry = new this.THREE.TubeGeometry(curve, 5000, 0.5, 4);
832
- //加载纹理
833
- lineTexture = new this.THREE.TextureLoader().load('/static/arrow/arrow-right.png');
834
- lineTexture.wrapS = this.THREE.RepeatWrapping;
835
- lineTexture.wrapT = this.THREE.RepeatWrapping;
836
- lineTexture.repeat.set(20, 1); //水平重复20次
837
- lineTexture.needsUpdate = true;
838
- lineTexture.offset.y = 0.5;
839
- const material = new this.THREE.MeshBasicMaterial({
840
- map: lineTexture,
841
- side: this.THREE.BackSide, //显示背面
842
- transparent: true,
843
- });
844
- let line = new this.THREE.Line(geometry, material);
845
- line.name = params.name;
846
- scene.add(line);
847
- clock = new this.THREE.Clock();
848
- }
849
- },
850
- //
851
- // 开启漫游
852
- /*
853
- 参数为Object
854
- loop: true / false是否进行循环模型 默认是false
855
- speed: number 漫游的速度,
856
- path: [{x: 0, y: 0, z: 0}] 漫游的坐标位置点,
857
- name: '', 路径的名称,用来清除用的
858
- */
859
- startRoam(params) {
860
- progress = 0;
861
- roamConfig = {
862
- loop: params.loop,
863
- speed: params.speed,
864
- };
865
- this.drawTextureCurve({
866
- name: params.name,
867
- path: params.path,
868
- });
869
- roaming = true;
870
- },
871
- // 更新漫游的配置
872
- /*
873
- 参数为Object
874
- */
875
- updateRoamConfig(params) {
876
- for (let key in params) {
877
- roamConfig[key] = params[key];
878
- }
879
- },
880
- // 结束/退出漫游
881
- endRoam() {
882
- roaming = false;
883
- progress = 0;
884
- this.removeObjectByName(roamConfig.name);
885
- roamConfig = {
886
- loop: false,
887
- speed: 0,
888
- name: '',
889
- };
890
- roaming = false;
891
- lineTexture = null;
892
- },
893
- // 相机跟随轨道
894
- cameraTrack() {
895
- if (roaming) {
896
- renderEnabled = true;
897
- lineTexture.offset.x -= 0.05;
898
- // 相机和控制器的偏移
899
- let offset = 10 / curve.getLength();
900
- // progress 取值范围为0~1。getPoint(0)表示曲线起点,getPoint(1)表示曲线终点
901
- if (progress <= 1 - offset) {
902
- // this.timeRender()
903
- const point = curve.getPointAt(progress);
904
- const pointBox = curve.getPointAt(progress + offset);
905
- camera.position.set(point.x, point.y + 5, point.z);
906
- camera.lookAt(pointBox.x, pointBox.y + 5, pointBox.z);
907
- cameraControls.setPosition(point.x, point.y + 5, point.z, false);
908
- cameraControls.setTarget(pointBox.x, pointBox.y + 5, pointBox.z, false);
909
- progress += roamConfig.speed / 300;
910
- } else {
911
- // 循环漫游
912
- if (roamConfig.loop) {
913
- progress = 0;
914
- }
915
- }
916
- } else {
917
- lineTexture = null;
918
- progress = 0;
919
- }
920
- },
921
- // 全局整体炸开
922
- /**
923
- * 参数val: 爆炸的值
924
- */
925
- globalBomb(val) {
926
- if (scene.children.length === 0) return;
927
- for (let i = 0; i < scene.children.length; i++) {
928
- scene.children[i].traverse(item => {
929
- if (!item.isMesh || !item.worldDir) return;
930
- // 爆炸公式
931
- this.computedBomb(item, val);
932
- });
933
- }
934
- // this.timeRender();
935
- },
936
- // 单个实体模型炸开
937
- /*
938
- 参数:
939
- name: '',当前选中的实体对象的id
940
- value: 0 - 100 整数
941
- */
942
- localBomb(name, value) {
943
- let target = scene.getObjectByName(name);
944
- if (target) {
945
- this.computedBomb(target, value);
946
- }
947
- },
948
- computedBomb(object, val) {
949
- let bombPosition = new this.THREE.Vector3()
950
- .copy(object.userData.oldPs)
951
- .add(new this.THREE.Vector3().copy(object.worldDir).multiplyScalar(val))
952
- .add(new this.THREE.Vector3().copy(object.userData.position));
953
- object.position.copy(bombPosition);
954
- },
955
- // 设置全局整体剖切
956
- /*
957
- 先开启模型全局剖切模式, 会返回剖切值的最大最小值
958
- 剖切值变换时, 使用
959
- */
960
- setGlobalClipping(flag = true) {
961
- const box3 = new this.THREE.Box3().setFromObject(scene.children[0]);
962
- let max = box3.max;
963
- let min = box3.min;
964
- const clippingPlanes = [
965
- new this.THREE.Plane(new this.THREE.Vector3(-1, 0, 0), max.x),
966
- new this.THREE.Plane(new this.THREE.Vector3(0, -1, 0), max.y),
967
- new this.THREE.Plane(new this.THREE.Vector3(0, 0, -1), max.z),
968
- ];
969
- renderer.clippingPlanes = clippingPlanes;
970
- if (flag) {
971
- guiParams = {
972
- x轴: clippingPlanes[0].constant,
973
- y轴: clippingPlanes[2].constant,
974
- z轴: clippingPlanes[1].constant,
975
- };
976
- this.addClippingGui(
977
- '全局剖切',
978
- {
979
- x: min.x,
980
- y: min.z,
981
- z: min.y,
982
- },
983
- clippingPlanes
984
- );
985
- }
986
- return {
987
- min: min,
988
- max: max,
989
- };
990
- },
991
- // 更新全局剖切的值
992
- /*
993
- 参数:Object
994
- {x: 1, y: 0, z: 0} 可以传一个也可以传多个
995
- */
996
- updateGlobalCliValue(params) {
997
- let axis = {
998
- x: 0,
999
- y: 1,
1000
- z: 2,
1001
- };
1002
- for (const key in params) {
1003
- renderer.clippingPlanes[axis[key]].constant = params[key];
1004
- }
1005
- // this.timeRender()
1006
- },
1007
- // 取消全局剖切
1008
- cancelGlobalClipping() {
1009
- guiParams = {
1010
- x轴: 0,
1011
- y轴: 0,
1012
- z轴: 0,
1013
- };
1014
- renderer.clippingPlanes = Object.freeze([]);
1015
- gui && gui.destroy();
1016
- },
1017
- // 单个实体的剖切/ 局部剖切 obj 实体对象 flag 代表是否使用图形组件自带的剖切面板 默认为true
1018
- setLocalClipping(obj, flag = true) {
1019
- clippingMesh.splice(0);
1020
- clippingMesh.push(obj);
1021
- renderer.localClippingEnabled = true;
1022
- // mesh的boundingBox 可以获取到该对象位置的最大最小值 但是在这里我们需要把y、z轴互换
1023
- const boundingBox = new this.THREE.Box3().copy(obj.boundingBox);
1024
- boundingBox.applyMatrix4(obj.matrixWorld);
1025
- let clippingPlanes = [
1026
- new this.THREE.Plane(new this.THREE.Vector3(-1, 0, 0), Math.ceil(boundingBox.max.x)),
1027
- new this.THREE.Plane(new this.THREE.Vector3(0, -1, 0), Math.ceil(boundingBox.max.y)),
1028
- new this.THREE.Plane(new this.THREE.Vector3(0, 0, -1), Math.ceil(boundingBox.max.z)),
1029
- ];
1030
- obj.material.clippingPlanes = clippingPlanes;
1031
- obj.material.needsUpdate = true;
1032
- // 将局部剖切的对象记录下来
1033
- if (flag) {
1034
- guiParams = {
1035
- x轴: clippingPlanes[0].constant,
1036
- y轴: clippingPlanes[2].constant,
1037
- z轴: clippingPlanes[1].constant,
1038
- };
1039
- this.addClippingGui(
1040
- '局部剖切',
1041
- {
1042
- x: Math.floor(boundingBox.min.x),
1043
- y: Math.floor(boundingBox.min.z),
1044
- z: Math.floor(boundingBox.min.y),
1045
- },
1046
- obj.material.clippingPlanes
1047
- );
1048
- // cube.material.clippingPlanes
1049
- }
1050
- return {
1051
- min: boundingBox.min,
1052
- max: boundingBox.max,
1053
- };
1054
- },
1055
- // 更新局部剖切的值
1056
- /*
1057
- 参数:Object
1058
- {x: 1, y: 0, z: 0} 可以传一个也可以传多个
1059
- */
1060
- updateLocalCliValue(params) {
1061
- let axis = {
1062
- x: 0,
1063
- y: 1,
1064
- z: 2,
1065
- };
1066
- let obj = clippingMesh[clippingMesh.length - 1];
1067
- for (const key in params) {
1068
- obj.material.clippingPlanes[axis[key]].constant = params[key];
1069
- obj.material.needsUpdate = true;
1070
- }
1071
- // this.timeRender()
1072
- },
1073
- // 取消局部/单个实体的剖切
1074
- cancelLocalClipping() {
1075
- guiParams = {
1076
- x轴: 0,
1077
- y轴: 0,
1078
- z轴: 0,
1079
- };
1080
- gui && gui.destroy();
1081
- clippingMesh.forEach(item => {
1082
- item.material.clippingPlanes = Object.freeze([]);
1083
- item.material.needsUpdate = true;
1084
- });
1085
- clippingMesh.splice(0);
1086
- },
1087
- // 添加剖切轴工具
1088
- addClippingGui(title, minValue, objClipp1, objClipp2) {
1089
- gui = new GUI({
1090
- title: title,
1091
- });
1092
- gui
1093
- .add(guiParams, 'x轴')
1094
- .min(minValue.x)
1095
- .max(guiParams['x轴'])
1096
- .onChange(d => {
1097
- objClipp1[0].constant = d;
1098
- objClipp2 && (objClipp2[0].constant = d);
1099
- });
1100
- gui
1101
- .add(guiParams, 'y轴')
1102
- .min(minValue.y)
1103
- .max(guiParams['y轴'])
1104
- .onChange(d => {
1105
- objClipp1[2].constant = d;
1106
- objClipp2 && (objClipp2[2].constant = d);
1107
- });
1108
- gui
1109
- .add(guiParams, 'z轴')
1110
- .min(minValue.z)
1111
- .max(guiParams['z轴'])
1112
- .onChange(d => {
1113
- objClipp1[1].constant = d;
1114
- });
1115
- },
1116
- // 开启第一视角
1117
- startFirstPer({ moveSpeed = 200, jumpSpeed = 200 }) {
1118
- removeSpeed = moveSpeed;
1119
- upSpeed = jumpSpeed;
1120
-
1121
- clock = new this.THREE.Clock();
1122
- downRaycaster = new this.THREE.Raycaster(
1123
- new this.THREE.Vector3(),
1124
- new this.THREE.Vector3(0, -1, 0),
1125
- 0,
1126
- 10
1127
- );
1128
- velocity = new this.THREE.Vector3(); //移动速度变量
1129
- direction = new this.THREE.Vector3(); //移动的方向变量
1130
- firstPerSign = true;
1131
- this.initPointerLock();
1132
- },
1133
- // 页面是否锁定
1134
- initPointerLock() {
1135
- this.home();
1136
- setTimeout(() => {
1137
- pointControls = new PointerLockControls(camera, renderer.domElement);
1138
- pointControls.lock();
1139
- // 锁定
1140
- pointControls.addEventListener('lock', () => {
1141
- cameraControls.enabled = false;
1142
- window.addEventListener('keydown', this.onKeyDown, false);
1143
- window.addEventListener('keyup', this.onKeyUp, false);
1144
- renderer.domElement.removeEventListener('mouseup', this.mouseClick, false);
1145
- renderer.domElement.removeEventListener('mousedown', this.mouseDown, false);
1146
- });
1147
- // 解锁
1148
- pointControls.addEventListener('unlock', () => {
1149
- firstPerSign = false;
1150
- cameraControls.enabled = true;
1151
- // 返回初始视角
1152
- cameraControls.reset(true);
1153
- cameraControls.update(0);
1154
- setTimeout(() => {
1155
- window.removeEventListener('keydown', this.onKeyDown);
1156
- window.removeEventListener('keyup', this.onKeyUp);
1157
- renderer.domElement.addEventListener('mouseup', this.mouseClick, false);
1158
- renderer.domElement.addEventListener('mousedown', this.mouseDown, false);
1159
- // this.timeRender()
1160
- }, 0);
1161
- });
1162
- scene.add(pointControls.object);
1163
- }, 10);
1164
- },
1165
- // 第一视角运动
1166
- firstPerspective() {
1167
- if (!cameraControls.enabled && firstPerSign) {
1168
- // 获取到控制器对象
1169
- let control = pointControls.object;
1170
- // 获取刷新时间
1171
- let delta = clock.getDelta();
1172
- // velocity每次的速度,为了保证有过渡
1173
- velocity.x -= velocity.x * 10.0 * delta;
1174
- velocity.z -= velocity.z * 10.0 * delta;
1175
- velocity.y -= 9.8 * 100.0 * delta; // 默认下降的速度
1176
- // 获取当前按键的方向并获取朝哪个方向移动
1177
- direction.z = Number(moveForward) - Number(moveBackward);
1178
- direction.x = Number(moveRight) - Number(moveLeft);
1179
- // 将法向量的值归一化
1180
- direction.normalize();
1181
- if (moveForward || moveBackward) velocity.z -= direction.z * removeSpeed * delta;
1182
- if (moveLeft || moveRight) velocity.x -= direction.x * removeSpeed * delta;
1183
- // }
1184
- // 复制相机的位置
1185
- downRaycaster.ray.origin.copy(control.position);
1186
- // 获取相机靠下5的位置
1187
- downRaycaster.ray.origin.y += 5;
1188
- // 判断是否停留在了立方体上面
1189
- let intersections = downRaycaster.intersectObjects(scene.children, true);
1190
- var onObject = intersections.length > 0;
1191
- if (onObject === true) {
1192
- velocity.y = Math.max(0, velocity.y);
1193
- canJump = true;
1194
- }
1195
- // 根据速度值移动控制器
1196
- pointControls.moveRight(-velocity.x * delta);
1197
- pointControls.moveForward(-velocity.z * delta);
1198
- control.position.y += velocity.y * delta;
1199
- // 保证控制器的y轴在平面上
1200
- if (control.position.y < 3 - 0 / 10) {
1201
- velocity.y = -0 / 10;
1202
- control.position.y = 3 - 0 / 10;
1203
- canJump = true;
1204
- }
1205
- }
1206
- },
1207
- // 键盘监听事件
1208
- onKeyDown(event) {
1209
- if (!event.keyCode) return;
1210
- switch (event.keyCode) {
1211
- // 前进
1212
- case 38:
1213
- case 87:
1214
- moveForward = true;
1215
- break;
1216
- // 向左
1217
- case 37:
1218
- case 65:
1219
- moveLeft = true;
1220
- break;
1221
- // 后退
1222
- case 40:
1223
- case 83:
1224
- moveBackward = true;
1225
- break;
1226
- // 向右
1227
- case 39:
1228
- case 68:
1229
- moveRight = true;
1230
- break;
1231
- // 跳跃
1232
- case 32:
1233
- if (canJump && spaceUp) velocity.y += upSpeed;
1234
- canJump = false;
1235
- spaceUp = false;
1236
- break;
1237
- }
1238
- },
1239
- onKeyUp(event) {
1240
- if (!event.keyCode) return;
1241
- // this.timeRender()
1242
- switch (event.keyCode) {
1243
- // 前进
1244
- case 38:
1245
- case 87:
1246
- moveForward = false;
1247
- break;
1248
- // 向左
1249
- case 37:
1250
- case 65:
1251
- moveLeft = false;
1252
- break;
1253
- // 后退
1254
- case 40:
1255
- case 83:
1256
- moveBackward = false;
1257
- break;
1258
- // 向右
1259
- case 39:
1260
- case 68:
1261
- moveRight = false;
1262
- break;
1263
- // 跳跃
1264
- case 32:
1265
- spaceUp = true;
1266
- break;
1267
- }
1268
- },
1269
- // 返回主视角/恢复相机初始状态
1270
- home() {
1271
- if (roaming) {
1272
- this.endRoam();
1273
- }
1274
- cameraControls.reset(true);
1275
- cameraControls.update(0);
1276
- // this.timeRender()
1277
- },
1278
- // 测量
1279
- /*
1280
- 参数: type: '', distance、area、angle, 暂时只提供距离、面积、角度这三种方式
1281
- */
1282
- openMeasure(type) {
1283
- if (threeMeasure) {
1284
- threeMeasure.close();
1285
- threeMeasure = null;
1286
- }
1287
- measureFlag = true;
1288
- renderEnabled = true;
1289
- switch (type) {
1290
- case 'distance':
1291
- threeMeasure = new MeasureDistance.MeasureDistance(
1292
- renderer,
1293
- scene,
1294
- camera,
1295
- instructions.offsetWidth,
1296
- instructions.offsetHeight
1297
- );
1298
- threeMeasure.start();
1299
- break;
1300
- case 'area':
1301
- threeMeasure = new MeasureArea.MeasureArea(
1302
- renderer,
1303
- scene,
1304
- camera,
1305
- instructions.offsetWidth,
1306
- instructions.offsetHeight
1307
- );
1308
- threeMeasure.start();
1309
- break;
1310
- case 'angle':
1311
- threeMeasure = new MeasureAngle.MeasureAngle(
1312
- renderer,
1313
- scene,
1314
- camera,
1315
- instructions.offsetWidth,
1316
- instructions.offsetHeight
1317
- );
1318
- threeMeasure.start();
1319
- break;
1320
- }
1321
- // 添加键盘事件监听器
1322
- document.addEventListener('keydown', this.handleMeasureKeyDown, false);
1323
- },
1324
- // 关闭测量
1325
- closeMeasure() {
1326
- measureFlag = false;
1327
- if (threeMeasure) {
1328
- threeMeasure.close();
1329
- threeMeasure = null;
1330
- // this.timeRender()
1331
- }
1332
- // 移除键盘事件监听器
1333
- document.removeEventListener('keydown', this.handleMeasureKeyDown, false);
1334
- },
1335
- handleMeasureKeyDown(event) {
1336
- // 检查是否按下了ESC键
1337
- const keyParam = {
1338
- desc: event.key,
1339
- keyCode: event.keyCode,
1340
- event,
1341
- };
1342
- if (event.keyCode === 27) {
1343
- // ESC键的键码是27
1344
- this.closeMeasure();
1345
- }
1346
- this.$emit('keyDown', keyParam);
1347
- },
1348
- // 开启平移
1349
- /*
1350
- 参数: object, 当前选中的对象, distance: {x: 0, y: 0, z: 0}, 平移的距离
1351
- */
1352
- translateMesh(object, distance) {
1353
- let translateMatrix = new this.THREE.Matrix4().makeTranslation(
1354
- distance.x,
1355
- distance.y,
1356
- distance.z
1357
- );
1358
- let translateMatrixInvert = new this.THREE.Matrix4().copy(translateMatrix).invert();
1359
- if (object.userData.translateMatrixInvert) {
1360
- object.applyMatrix4(object.userData.translateMatrixInvert);
1361
- }
1362
- object.userData.translateMatrixInvert = translateMatrixInvert;
1363
- object.applyMatrix4(translateMatrix);
1364
- object.userData.position = new this.THREE.Vector3().copy(object.position);
1365
- object.userData.translate = distance;
1366
- },
1367
- // 单个模型对象旋转
1368
- /*
1369
- 参数: object, 目标实体, degrees: {x: 0, y: 0, z: 0}, 旋转的数值(0-360)
1370
- */
1371
- rotateMesh(object, degrees) {
1372
- // 在矩阵的计算里 完成撤销操作是向量去乘以一个矩阵的逆矩阵
1373
- // 变化后的向量 = 原始向量 * 变化矩阵
1374
- // 逆矩阵 = 变化矩阵.inverse()
1375
- // 原始向量 = 变化后的向量 * 逆矩阵
1376
- let center = object.userData.center;
1377
- let v = new this.THREE.Vector3(center.x, center.y, center.z).negate();
1378
- let translateMatrix = new this.THREE.Matrix4().makeTranslation(v.x, v.y, v.z);
1379
- let rotateX = new this.THREE.Matrix4().makeRotationX(degrees.x * (Math.PI / 180));
1380
- let rotateY = new this.THREE.Matrix4().makeRotationY(degrees.y * (Math.PI / 180));
1381
- let rotateZ = new this.THREE.Matrix4().makeRotationZ(degrees.z * (Math.PI / 180));
1382
- let combineMatrix = new this.THREE.Matrix4()
1383
- .multiply(rotateX)
1384
- .multiply(rotateY)
1385
- .multiply(rotateZ);
1386
- object.applyMatrix4(translateMatrix);
1387
- let combineMatrixInvert = new this.THREE.Matrix4().copy(combineMatrix).invert();
1388
- if (object.userData.combineMatrixInvert) {
1389
- object.applyMatrix4(object.userData.combineMatrixInvert);
1390
- }
1391
- object.userData.combineMatrixInvert = combineMatrixInvert;
1392
- object.applyMatrix4(combineMatrix);
1393
- let translateM = new this.THREE.Matrix4().makeTranslation(center.x, center.y, center.z);
1394
- object.applyMatrix4(translateM);
1395
- object.userData.position = new this.THREE.Vector3().copy(object.position);
1396
- object.userData.rotate = degrees;
1397
- },
1398
- // 隔离
1399
- /*
1400
- 参数: object, 目标实体,
1401
- */
1402
- isolate(object) {
1403
- // 隔离 将目标实体以外的实体隐藏掉
1404
- scene.traverse(item => {
1405
- if (item.isMesh && item.name !== object.name) {
1406
- const offsetMatrix = new this.THREE.Matrix4()
1407
- .copy(item.userData.copyMatrix)
1408
- .makeTranslation(9999999, 9999999, 9999999);
1409
- item.setMatrixAt(item.userData.instanceIndex, offsetMatrix);
1410
- item.instanceMatrix.needsUpdate = true;
1411
- }
1412
- });
1413
- },
1414
- // 还原操作 将修改过的实体进行恢复
1415
- restore() {
1416
- scene.traverse(item => {
1417
- if (item.isMesh) {
1418
- item.setColorAt(item.userData.instanceIndex, item.material.userData.nColor);
1419
- item.instanceColor.needsUpdate = true;
1420
- item.userData.translate = {
1421
- x: 0,
1422
- y: 0,
1423
- z: 0,
1424
- };
1425
- item.userData.rotate = {
1426
- x: 0,
1427
- y: 0,
1428
- z: 0,
1429
- };
1430
- if (item.userData.translateMatrixInvert) {
1431
- item.applyMatrix4(item.userData.translateMatrixInvert);
1432
- item.userData.translateMatrixInvert = null;
1433
- }
1434
- if (item.userData.combineMatrixInvert) {
1435
- this.rotateMesh(item, {
1436
- x: 0,
1437
- y: 0,
1438
- z: 0,
1439
- });
1440
- item.userData.combineMatrixInvert = null;
1441
- }
1442
- const offsetMatrix = new this.THREE.Matrix4().copy(item.userData.copyMatrix);
1443
- item.setMatrixAt(item.userData.instanceIndex, offsetMatrix);
1444
- item.instanceMatrix.needsUpdate = true;
1445
- }
1446
- });
1447
- },
1448
- // 添加自定义模型, 暂时只支持glb、gltf格式
1449
- addCustomModel(name, position, url, scale = 1, immediately = false, callback) {
1450
- const loader = new GLTFLoader();
1451
- let locationModel = null;
1452
- loader.load(url, gltf => {
1453
- locationModel = gltf.scene;
1454
- locationModel.scale.set(scale, scale, scale);
1455
- locationModel.updateMatrixWorld();
1456
- if (immediately) {
1457
- cameraControls.fitToSphere(gltf.scene, true);
1458
- }
1459
- // 动画混合器
1460
- // 不参与裁剪
1461
- locationModel.userData.cull = false;
1462
- locationModel.name = name;
1463
- scene.add(locationModel);
1464
- locationModel.position.copy(new this.THREE.Vector3(position.x, position.y, position.z));
1465
- if (gltf.animations.length > 0) {
1466
- let actionMixer = new this.THREE.AnimationMixer(gltf.scene);
1467
- const walkActive = actionMixer.clipAction(gltf.animations[0]);
1468
- walkActive.play();
1469
- modelActive.push(walkActive);
1470
- modelActions.push(actionMixer);
1471
- }
1472
- callback && callback();
1473
- });
1474
- },
1475
- // 删除添加的自定义模型
1476
- removeCustomModel(name) {
1477
- let obj = this.getObjectByName(name);
1478
- obj.forEach((item, index) => {
1479
- if (item.animations > 0) {
1480
- item.removeFromParent();
1481
- modelActions[index].uncacheRoot(item);
1482
- modelActions[index].uncacheRoot(modelActive[index]);
1483
- }
1484
- item.traverse(child => {
1485
- if (child instanceof this.THREE.Mesh) {
1486
- child.geometry.dispose();
1487
- child.material.dispose();
1488
- }
1489
- });
1490
- scene.remove(item);
1491
- });
1492
- modelActions.splice(0);
1493
- modelActive.splice(0);
1494
- },
1495
- // 修改天空背景
1496
- skyBoxScene(url) {
1497
- const textureCube = new this.THREE.CubeTextureLoader().load(url);
1498
- scene.background = textureCube;
1499
- },
1500
- // 清除天空背景
1501
- clearSkyBoxScene() {
1502
- scene.background = null;
1503
- },
1504
- // 下雨模拟
1505
- sceneSimu(type) {
1506
- if (!outlineComposer) {
1507
- outlineComposer = new EffectComposer(renderer, renderTarget);
1508
- outlineComposer.addPass(new RenderPass(scene, camera));
1509
- }
1510
- if (type === 'rain') {
1511
- scenePass = new ShaderPass(RainShader);
1512
- scenePass.uniforms['iResolution'].value = new this.THREE.Vector2(
1513
- window.innerWidth,
1514
- window.innerHeight
1515
- );
1516
- outlineComposer.addPass(scenePass);
1517
- } else if (type === 'snow') {
1518
- scenePass = new ShaderPass(SnowShader);
1519
- scenePass.uniforms['iResolution'].value = new this.THREE.Vector2(
1520
- window.innerWidth,
1521
- window.innerHeight
1522
- );
1523
- outlineComposer.addPass(scenePass);
1524
- }
1525
- },
1526
- clearSceneSim() {
1527
- if (scenePass && outlineComposer) {
1528
- outlineComposer.removePass(scenePass);
1529
- }
1530
- scenePass = null;
1531
- },
1532
- // 获取中心点
1533
- getCenter(obj) {
1534
- let center = new this.THREE.Vector3();
1535
- obj.boundingBox.getCenter(center);
1536
- if (obj.userData.is3D) {
1537
- center.applyMatrix4(mat4);
1538
- }
1539
- return center;
1540
- },
1541
- getSize(obj) {
1542
- let size = new this.THREE.Vector3();
1543
- obj.boundingBox.getSize(size);
1544
- if (obj.userData.is3D) {
1545
- size.applyMatrix4(mat4);
1546
- }
1547
- return size;
1548
- },
1549
- // 动画
1550
- renderBegin() {
1551
- // 渲染第一帧
1552
- console.log('渲染第一帧');
1553
- renderer.setRenderTarget(postTarget);
1554
- renderer.clearRenderState(scene, camera);
1555
- renderer.clear(true, true, true);
1556
- renderer.render(scene, camera, false);
1557
- renderer.setRenderTarget(null);
1558
- renderer.clear(true, true, true);
1559
- renderer.render(postScene, postCamera);
1560
- },
1561
-
1562
- renderResidual() {
1563
- // 剩余部分
1564
- console.log('剩余部分');
1565
- renderer.setRenderTarget(postTarget);
1566
- renderer.render(scene, camera);
1567
- renderer.setRenderTarget(null);
1568
- renderer.clear(true, true, true);
1569
- renderer.render(postScene, postCamera);
1570
- },
1571
- animate() {
1572
- const delta = fpsClock.getDelta();
1573
- timeStamp += delta;
1574
- animateId = requestAnimationFrame(this.animate);
1575
- if (timeStamp > singleFrameTime) {
1576
- if (modelActions.length > 0) {
1577
- modelActions.forEach(item => {
1578
- item.update(timeStamp);
1579
- });
1580
- }
1581
- cameraControls.enabled && cameraControls.update(timeStamp);
1582
- this.cameraTrack();
1583
- this.firstPerspective();
1584
- if (scenePass) {
1585
- let d = sceneClock.getDelta();
1586
- scenePass.uniforms['iTime'].value += d;
1587
- }
1588
- labelRenderer.render(scene, camera);
1589
- if (renderEnabled) {
1590
- this.renderBegin();
1591
- } else {
1592
- if (!scene.renderStage.isComplete()) {
1593
- this.renderResidual();
1594
- } else {
1595
- renderEnabled = false;
1596
- if (sceneChangeFlag) {
1597
- renderer.setRenderTarget(postTarget);
1598
- renderer.renderIncrement(scene, camera);
1599
- renderer.setRenderTarget(null);
1600
- renderer.clear(true, true, true);
1601
- renderer.render(postScene, postCamera);
1602
- sceneChangeFlag = false;
1603
- }
1604
- }
1605
- }
1606
- // renderer.setRenderTarget(null)
1607
- timeStamp = timeStamp % singleFrameTime;
1608
- outlineComposer && outlineComposer.render();
1609
- }
1610
- },
1611
- // 暴露个别参数让业务自己做特殊业务。
1612
- // 后续若多个业务有相同使用场景,再抽象至公共组件中。
1613
- exportParmas() {
1614
- return {
1615
- renderer,
1616
- camera,
1617
- cameraControls,
1618
- scene,
1619
- };
1620
- },
1621
- setMouseAction(btn) {
1622
- const ACTION_ENUM = {
1623
- none: 0,
1624
- rotate: 1,
1625
- drag: 2,
1626
- };
1627
- const { left, right } = btn;
1628
-
1629
- left && (cameraControls.mouseButtons.left = ACTION_ENUM[left]);
1630
- right && (cameraControls.mouseButtons.right = ACTION_ENUM[right]);
1631
- },
1632
- },
1633
- };
1634
- </script>
1635
- <style lang="scss" scoped>
1636
- #fl-model {
1637
- width: 100%;
1638
- height: 100%;
1639
- cursor: pointer;
1640
- }
1641
- ::v-deep .tips-label {
1642
- width: 60px;
1643
- color: #000;
1644
- font: 12px Helvetica;
1645
- margin-top: -3em;
1646
- padding: 5px;
1647
- text-align: center;
1648
- vertical-align: middle;
1649
- background-color: khaki;
1650
- }
1651
-
1652
- ::v-deep .measure-label {
1653
- max-width: 100px;
1654
- margin-top: -1em;
1655
- border: 10px;
1656
- border-radius: 5px;
1657
- padding: 3px 10px;
1658
- cursor: pointer;
1659
- color: rgb(0, 155, 234);
1660
- background-color: rgb(244, 244, 244);
1661
- box-shadow: 0px 1px 3px 1px rgba(0, 0, 0, 0.25);
1662
- }
1663
- ::v-deep .circle-tag {
1664
- width: 10px;
1665
- height: 10px;
1666
- margin-top: 5px;
1667
- border-radius: 50%;
1668
- background-color: #ff5000;
1669
- }
1670
- ::v-deep .measure-label-font {
1671
- word-break: break-all;
1672
- }
1673
-
1674
- ::v-deep .mark-label-img {
1675
- padding-top: 5px;
1676
- width: 20px;
1677
- height: 20px;
1678
- }
1679
- </style>