fl-web-component 1.4.8 → 2.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -30
- package/dist/fl-web-component.common.1.js +2 -2
- package/dist/fl-web-component.common.1.js.map +1 -1
- package/dist/fl-web-component.common.2.js.map +1 -1
- package/dist/fl-web-component.common.js +77408 -47295
- package/dist/fl-web-component.common.js.map +1 -1
- package/dist/fl-web-component.css +1 -1
- package/package.json +12 -4
- package/packages/components/com-graphics/box.json +77 -0
- package/packages/components/com-graphics/component/ann-tool.vue +465 -0
- package/packages/components/com-graphics/index copy.vue +1679 -0
- package/packages/components/com-graphics/index.vue +3890 -301
- package/packages/components/com-graphics/pid.vue +210 -44
- package/packages/components/com-graphics/test.html +127 -0
- package/packages/components/com-tiles/index.vue +187 -0
- package/packages/utils/StreamLoader.js +1498 -0
- package/packages/utils/StreamLoaderParser.worker.js +595 -0
- package/patches/camera-controls+2.9.0.patch +63 -63
- package/src/main.js +9 -1
- package/src/static/ann-img/mark_circle@2x.png +0 -0
- package/src/static/ann-img/mark_clear@2x.png +0 -0
- package/src/static/ann-img/mark_cloud@2x.png +0 -0
- package/src/static/ann-img/mark_color@2x.png +0 -0
- package/src/static/ann-img/mark_eraser@2x.png +0 -0
- package/src/static/ann-img/mark_exit@2x.png +0 -0
- package/src/static/ann-img/mark_finish@2x.png +0 -0
- package/src/static/ann-img/mark_font@2x.png +0 -0
- package/src/static/ann-img/mark_polyline@2x.png +0 -0
- package/src/static/ann-img/mark_rectangle@2x.png +0 -0
- package/src/static/ann-img/mark_zoomin@2x.png +0 -0
- package/src/static/ann-img/mark_zoomout@2x.png +0 -0
- package/src/utils/cloud.js +110 -0
- package/src/utils/cursor.js +10 -0
- package/src/utils/flgltf-parser.js +245 -193
- package/src/utils/instance-parser.js +718 -170
- package/dist/fl-web-component.common.3.js +0 -7740
- package/dist/fl-web-component.common.3.js.map +0 -1
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<div id="svg-component" ref="svgComponent">
|
|
3
|
+
<!-- 批注工具条 -->
|
|
4
|
+
<div v-if="toolbarShow" id="toolbar-show">
|
|
5
|
+
<AnnTool ref="AnnToolbar" @closeAnn="closeAnnMode" :src="currentSvg"></AnnTool>
|
|
6
|
+
</div>
|
|
7
|
+
<embed v-show="canvasShow" type="image/svg+xml" id="svg-tigger" :src="currentSvg" />
|
|
8
|
+
</div>
|
|
9
|
+
|
|
3
10
|
</template>
|
|
4
11
|
<script>
|
|
5
12
|
import svgPanZoom from 'svg-pan-zoom';
|
|
13
|
+
import html2canvas from 'html2canvas';
|
|
14
|
+
import AnnTool from './component/ann-tool.vue'
|
|
6
15
|
var svgCon = {},
|
|
7
16
|
svgTigger = {},
|
|
8
17
|
svgEmbed = {};
|
|
@@ -25,46 +34,61 @@
|
|
|
25
34
|
this.currentSvg = newVal
|
|
26
35
|
}
|
|
27
36
|
},
|
|
37
|
+
components: {
|
|
38
|
+
AnnTool
|
|
39
|
+
},
|
|
28
40
|
data() {
|
|
29
41
|
return {
|
|
30
42
|
currentSvg: '',
|
|
43
|
+
activeObj: null,
|
|
44
|
+
canvasShow: true,
|
|
45
|
+
toolbarShow: false,
|
|
46
|
+
svgInitStyle: {}
|
|
31
47
|
};
|
|
32
48
|
},
|
|
33
49
|
created() {
|
|
34
50
|
this.currentSvg = this.src
|
|
35
51
|
},
|
|
36
52
|
mounted() {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
this.
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
preTargetStyle.splice(0)
|
|
44
|
-
svgTigger = svgPanZoom('#svg-tigger', {
|
|
45
|
-
viewportSelector: '.svg-pan-zoom_viewport',
|
|
46
|
-
panEnabled: true,
|
|
47
|
-
zoomEnabled: true,
|
|
48
|
-
preventMouseEventsDefault: false,
|
|
53
|
+
this.$nextTick(() => {
|
|
54
|
+
svgEmbed = document.getElementById('svg-tigger');
|
|
55
|
+
svgEmbed.style.width = this.$refs.svgComponent.clientWidth + 'px'
|
|
56
|
+
svgEmbed.style.height = this.$refs.svgComponent.clientHeight + 'px'
|
|
57
|
+
svgEmbed.addEventListener('resize', () => {
|
|
58
|
+
this.onWindowResize;
|
|
49
59
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
60
|
+
svgEmbed.addEventListener('load', () => {
|
|
61
|
+
preTargetCon.splice(0)
|
|
62
|
+
preTargetStyle.splice(0)
|
|
63
|
+
svgTigger = svgPanZoom('#svg-tigger', {
|
|
64
|
+
viewportSelector: '.svg-pan-zoom_viewport',
|
|
65
|
+
panEnabled: true,
|
|
66
|
+
zoomEnabled: true,
|
|
67
|
+
preventMouseEventsDefault: false,
|
|
68
|
+
fit: true,
|
|
69
|
+
center: true
|
|
70
|
+
});
|
|
71
|
+
svgCon = svgEmbed.getSVGDocument().querySelector('svg');
|
|
72
|
+
svgCon.style = 'cursor: pointer;user-select: none;';
|
|
73
|
+
this.svgInitStyle = svgEmbed.getSVGDocument().getElementsByClassName("svg-pan-zoom_viewport")[0].getBoundingClientRect()
|
|
74
|
+
svgCon.setAttribute('viewBox', '0 0 ' + svgEmbed.offsetWidth + ' ' + svgEmbed.offsetHeight);
|
|
75
|
+
inspectionRect = svgCon.createSVGRect();
|
|
76
|
+
inspectionRect.width = 10;
|
|
77
|
+
inspectionRect.height = 10;
|
|
78
|
+
pointerRect = svgCon.createSVGPoint();
|
|
79
|
+
this.onWindowResize();
|
|
80
|
+
// 禁止右键菜单栏
|
|
81
|
+
svgCon.addEventListener('contextmenu', e => {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
});
|
|
84
|
+
// 鼠标的按下与抬起事件
|
|
85
|
+
svgCon.addEventListener('mousedown', this.mousedown);
|
|
86
|
+
svgCon.addEventListener('mouseup', this.mouseup);
|
|
87
|
+
// 加载完成后会通知
|
|
88
|
+
this.$emit('loaded');
|
|
61
89
|
});
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
svgCon.addEventListener('mouseup', this.mouseup);
|
|
65
|
-
// 加载完成后会通知
|
|
66
|
-
this.$emit('loaded');
|
|
67
|
-
});
|
|
90
|
+
})
|
|
91
|
+
|
|
68
92
|
},
|
|
69
93
|
methods: {
|
|
70
94
|
changeSvg(url) {
|
|
@@ -83,6 +107,7 @@
|
|
|
83
107
|
},
|
|
84
108
|
// 鼠标抬起
|
|
85
109
|
mouseup(evt) {
|
|
110
|
+
evt.stopPropagation()
|
|
86
111
|
lastTime = new Date().getTime();
|
|
87
112
|
let targetElement = null;
|
|
88
113
|
// 判断鼠标按下到抬起的时间间隔是否超过300ms 没有超过 则判断为点击
|
|
@@ -99,13 +124,30 @@
|
|
|
99
124
|
targetElement =
|
|
100
125
|
intersectionList.length > 0 ? intersectionList[intersectionList.length - 1] : null;
|
|
101
126
|
}
|
|
127
|
+
if (targetElement && !targetElement.id) {
|
|
128
|
+
this.searchSvgId(targetElement.parentNode)
|
|
129
|
+
} else {
|
|
130
|
+
this.activeObj = targetElement
|
|
131
|
+
}
|
|
102
132
|
if (evt.button === 0) {
|
|
103
|
-
this.$emit('leftClick',
|
|
133
|
+
this.$emit('leftClick', this.activeObj);
|
|
104
134
|
} else if (evt.button === 2) {
|
|
105
|
-
|
|
135
|
+
console.log('rightClick')
|
|
136
|
+
this.$emit('rightClick', {
|
|
137
|
+
event: evt,
|
|
138
|
+
targetObj: this.activeObj
|
|
139
|
+
});
|
|
106
140
|
}
|
|
107
141
|
}
|
|
108
142
|
},
|
|
143
|
+
// 查找对象的id
|
|
144
|
+
searchSvgId(dom) {
|
|
145
|
+
if (!dom.id) {
|
|
146
|
+
this.searchSvgId(dom.parentNode)
|
|
147
|
+
} else if (dom.id) {
|
|
148
|
+
this.activeObj = dom
|
|
149
|
+
}
|
|
150
|
+
},
|
|
109
151
|
// 高亮目标元素
|
|
110
152
|
/*
|
|
111
153
|
参数:svgIds: [], 需要高亮的id的集合, flag: true / false, 是否要定位目标元素, color: '', 高亮的颜色
|
|
@@ -117,6 +159,7 @@
|
|
|
117
159
|
for (let index = 0; index < svgIds.length; index++) {
|
|
118
160
|
let element = svgIds[index];
|
|
119
161
|
let targetCon = svgCon.getElementById(element.svgId);
|
|
162
|
+
console.log(targetCon)
|
|
120
163
|
if (!targetCon) return;
|
|
121
164
|
this.depthTraversal(targetCon.children, color);
|
|
122
165
|
}
|
|
@@ -124,7 +167,7 @@
|
|
|
124
167
|
svgTigger.reset();
|
|
125
168
|
let firstCon = svgCon.getElementById(svgIds[0].svgId);
|
|
126
169
|
setTimeout(() => {
|
|
127
|
-
svgTigger.zoomAtPoint(
|
|
170
|
+
svgTigger.zoomAtPoint(4, {
|
|
128
171
|
x: firstCon.getBoundingClientRect().x,
|
|
129
172
|
y: firstCon.getBoundingClientRect().y,
|
|
130
173
|
});
|
|
@@ -134,32 +177,110 @@
|
|
|
134
177
|
},
|
|
135
178
|
// pid组高亮 深度遍历
|
|
136
179
|
depthTraversal(svgCon, color) {
|
|
180
|
+
|
|
137
181
|
for (let i = 0; i < svgCon.length; i++) {
|
|
138
182
|
if (svgCon[i].children.length > 0) {
|
|
139
183
|
this.depthTraversal(svgCon[i].children, color);
|
|
140
184
|
} else {
|
|
141
185
|
preTargetCon.push(svgCon[i]);
|
|
142
186
|
preTargetStyle.push({
|
|
143
|
-
fillOpacity: svgCon[i].style.fillOpacity,
|
|
187
|
+
// fillOpacity: svgCon[i].style.fillOpacity,
|
|
144
188
|
stroke: svgCon[i].style.stroke,
|
|
145
|
-
strokeWidth: svgCon[i].style.strokeWidth,
|
|
189
|
+
// strokeWidth: svgCon[i].style.strokeWidth,
|
|
190
|
+
fill: svgCon[i].style.fill
|
|
146
191
|
});
|
|
147
|
-
svgCon[i].style.fillOpacity = '0';
|
|
192
|
+
// svgCon[i].style.fillOpacity = '0';
|
|
148
193
|
svgCon[i].style.stroke = color;
|
|
149
|
-
svgCon[i].style.
|
|
194
|
+
if (svgCon[i].nodeName === 'text' || (svgCon[i].style.fill && svgCon[i].style.fill!== 'none')) {
|
|
195
|
+
(svgCon[i].style.fill = color)
|
|
196
|
+
}
|
|
197
|
+
// svgCon[i].style.strokeWidth = 0.5;
|
|
150
198
|
}
|
|
151
199
|
}
|
|
152
200
|
},
|
|
153
201
|
// 清除上一次的高亮
|
|
154
202
|
resetHightLight() {
|
|
155
203
|
for (let i = 0; i < preTargetCon.length; i++) {
|
|
156
|
-
preTargetCon[i].style.fillOpacity = preTargetStyle[i].fillOpacity;
|
|
204
|
+
// preTargetCon[i].style.fillOpacity = preTargetStyle[i].fillOpacity;
|
|
157
205
|
preTargetCon[i].style.stroke = preTargetStyle[i].stroke;
|
|
158
|
-
preTargetCon[i].style.strokeWidth = preTargetStyle[i].strokeWidth;
|
|
206
|
+
// preTargetCon[i].style.strokeWidth = preTargetStyle[i].strokeWidth;
|
|
207
|
+
preTargetCon[i].style.fill = preTargetStyle[i].fill
|
|
159
208
|
}
|
|
160
209
|
preTargetStyle.splice(0);
|
|
161
210
|
preTargetCon.splice(0);
|
|
162
211
|
},
|
|
212
|
+
// 修改颜色要区分text text填充为fill
|
|
213
|
+
// 设置对象的属性 obj是需要被修改的对象, properties是需要修改的属性{}
|
|
214
|
+
setPorperty(obj, properties) {
|
|
215
|
+
if (obj.children.length > 0) {
|
|
216
|
+
let arr = obj.children
|
|
217
|
+
for (let index = 0; index < arr.length; index++) {
|
|
218
|
+
const element = arr[index]
|
|
219
|
+
if (element.nodeName !== 'g') {
|
|
220
|
+
for (const key in properties) {
|
|
221
|
+
// 记录一下原始状态
|
|
222
|
+
element.setAttribute(`data-${key}`, element['style'][key])
|
|
223
|
+
if (key === 'fill') {
|
|
224
|
+
if (element.nodeName === 'text' || (element.style.fill && element.style.fill !== 'none')) {
|
|
225
|
+
element['style']['fill'] = properties[key]
|
|
226
|
+
}
|
|
227
|
+
} else {
|
|
228
|
+
element['style'][key] = properties[key]
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (element.children && element.children.length > 0) {
|
|
233
|
+
this.setPorperty(element, properties)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
if (obj.nodeName !== 'g') {
|
|
238
|
+
for (const key in properties) {
|
|
239
|
+
if (key === 'fill') {
|
|
240
|
+
if (element.nodeName === 'text' || (element.style.fill && element.style.fill !== 'none')) {
|
|
241
|
+
element['style']['fill'] = properties[fill]
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
element['style'][key] = properties[key]
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
resetPorperty(obj, properties) {
|
|
252
|
+
if (obj.children.length > 0) {
|
|
253
|
+
let arr = obj.children
|
|
254
|
+
for (let index = 0; index < arr.length; index++) {
|
|
255
|
+
const element = arr[index]
|
|
256
|
+
if (element.nodeName !== 'g') {
|
|
257
|
+
properties.forEach(item => {
|
|
258
|
+
element['style'][item] = element.getAttribute(`data-${item}`)
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
if (element.children && element.children.length > 0) {
|
|
262
|
+
this.resetPorperty(element, properties)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
if (obj.nodeName !== 'g') {
|
|
267
|
+
properties.forEach(item => {
|
|
268
|
+
element['style'][item] = element.getAttribute(`data-${item}`)
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
getAllNode() {
|
|
274
|
+
let node = svgCon.querySelector('.svg-pan-zoom_viewport')
|
|
275
|
+
if (node) {
|
|
276
|
+
return node.children
|
|
277
|
+
} else {
|
|
278
|
+
return []
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
getNodeById(id) {
|
|
282
|
+
return svgCon.getElementById(id)
|
|
283
|
+
},
|
|
163
284
|
// 更新图纸/切换图纸
|
|
164
285
|
/*
|
|
165
286
|
参数:url: 图纸的路径
|
|
@@ -171,13 +292,58 @@
|
|
|
171
292
|
inspectionRect = null;
|
|
172
293
|
pointerRect = null;
|
|
173
294
|
},
|
|
295
|
+
// 图纸恢复到主视图
|
|
296
|
+
resetView() {
|
|
297
|
+
svgTigger.reset()
|
|
298
|
+
},
|
|
299
|
+
saveCanvas(canvasStyle) {
|
|
300
|
+
this.$refs.AnnToolbar.toolbarShow = false // 关闭画板工具条
|
|
301
|
+
svgTigger.reset()
|
|
302
|
+
svgTigger.zoom(canvasStyle.width / this.svgInitStyle.width)
|
|
303
|
+
svgTigger.pan({ x: -(canvasStyle.scrollLeft), y: -(canvasStyle.scrollTop) })
|
|
304
|
+
this.$nextTick(() => {
|
|
305
|
+
this.screenShot() // 保存批注
|
|
306
|
+
})
|
|
307
|
+
},
|
|
308
|
+
// 截图导出
|
|
309
|
+
screenShot() {
|
|
310
|
+
let shotObj = this.$refs.svgComponent
|
|
311
|
+
html2canvas(shotObj, { allowTaint: true, useCORS: true}).then(canvas => {
|
|
312
|
+
const link = document.createElement('a');
|
|
313
|
+
link.href = canvas.toDataURL('image/png');
|
|
314
|
+
link.download = 'screenshot.png';
|
|
315
|
+
link.click();
|
|
316
|
+
})
|
|
317
|
+
},
|
|
318
|
+
// 打开批注模式
|
|
319
|
+
openAnnToolbar() {
|
|
320
|
+
console.log('openAnnToolbar')
|
|
321
|
+
this.canvasShow = false
|
|
322
|
+
this.toolbarShow = true
|
|
323
|
+
},
|
|
324
|
+
closeAnnMode() {
|
|
325
|
+
this.canvasShow = true
|
|
326
|
+
this.toolbarShow = false
|
|
327
|
+
this.$emit('closeAnn')
|
|
328
|
+
}
|
|
174
329
|
},
|
|
175
330
|
};
|
|
176
331
|
</script>
|
|
177
332
|
<style lang="scss" scoped>
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
333
|
+
#svg-component, #svg-tigger {
|
|
334
|
+
cursor: pointer;
|
|
335
|
+
height: 100%;
|
|
336
|
+
width: 100%;
|
|
337
|
+
position: relative;
|
|
338
|
+
}
|
|
339
|
+
#toolbar-show{
|
|
340
|
+
z-index: 20;
|
|
341
|
+
position: absolute;
|
|
342
|
+
width: 100%;
|
|
343
|
+
height: 100%;
|
|
344
|
+
top: 0;
|
|
345
|
+
left: 0;
|
|
346
|
+
overflow: hidden;
|
|
347
|
+
background: #fff;
|
|
348
|
+
}
|
|
183
349
|
</style>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Three.js OutlinePass 点击高亮 Demo</title>
|
|
6
|
+
<style>
|
|
7
|
+
body { margin: 0; overflow: hidden; background-color: #000; }
|
|
8
|
+
canvas { display: block; }
|
|
9
|
+
.info { position: absolute; top: 10px; left: 10px; color: white; font-family: sans-serif; pointer-events: none; }
|
|
10
|
+
</style>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div class="info">点击立方体或球体触发 OutlinePass 高亮</div>
|
|
14
|
+
|
|
15
|
+
<script type="importmap">
|
|
16
|
+
{
|
|
17
|
+
"imports": {
|
|
18
|
+
"three": "https://unpkg.com/three@0.160.0/build/three.module.js",
|
|
19
|
+
"three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
</script>
|
|
23
|
+
|
|
24
|
+
<script type="module">
|
|
25
|
+
import * as THREE from 'three';
|
|
26
|
+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
27
|
+
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
|
|
28
|
+
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
|
|
29
|
+
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js';
|
|
30
|
+
|
|
31
|
+
// --- 1. 基础场景初始化 ---
|
|
32
|
+
const scene = new THREE.Scene();
|
|
33
|
+
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
34
|
+
camera.position.set(0, 5, 10);
|
|
35
|
+
|
|
36
|
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
37
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
38
|
+
renderer.setPixelRatio(window.devicePixelRatio);
|
|
39
|
+
document.body.appendChild(renderer.domElement);
|
|
40
|
+
|
|
41
|
+
const controls = new OrbitControls(camera, renderer.domElement);
|
|
42
|
+
|
|
43
|
+
// 灯光
|
|
44
|
+
scene.add(new THREE.AmbientLight(0x404040, 2));
|
|
45
|
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
|
|
46
|
+
directionalLight.position.set(5, 5, 5);
|
|
47
|
+
scene.add(directionalLight);
|
|
48
|
+
|
|
49
|
+
// --- 2. 添加测试模型 ---
|
|
50
|
+
const geometry1 = new THREE.BoxGeometry(2, 2, 2);
|
|
51
|
+
const material1 = new THREE.MeshStandardMaterial({ color: 0x44aa88 });
|
|
52
|
+
const cube = new THREE.Mesh(geometry1, material1);
|
|
53
|
+
cube.position.x = -3;
|
|
54
|
+
scene.add(cube);
|
|
55
|
+
|
|
56
|
+
const geometry2 = new THREE.SphereGeometry(1.5, 32, 32);
|
|
57
|
+
const material2 = new THREE.MeshStandardMaterial({ color: 0xaa4488 });
|
|
58
|
+
const sphere = new THREE.Mesh(geometry2, material2);
|
|
59
|
+
sphere.position.x = 3;
|
|
60
|
+
scene.add(sphere);
|
|
61
|
+
|
|
62
|
+
// --- 3. 配置后期处理 (OutlinePass) ---
|
|
63
|
+
const composer = new EffectComposer(renderer);
|
|
64
|
+
|
|
65
|
+
// 常规渲染通道
|
|
66
|
+
const renderPass = new RenderPass(scene, camera);
|
|
67
|
+
composer.addPass(renderPass);
|
|
68
|
+
|
|
69
|
+
// 轮廓线通道
|
|
70
|
+
const outlinePass = new OutlinePass(
|
|
71
|
+
new THREE.Vector2(window.innerWidth, window.innerHeight),
|
|
72
|
+
scene,
|
|
73
|
+
camera
|
|
74
|
+
);
|
|
75
|
+
// 配置轮廓线样式
|
|
76
|
+
outlinePass.edgeStrength = 8.0; // 边框强度
|
|
77
|
+
outlinePass.edgeGlow = 1.0; // 发光度
|
|
78
|
+
outlinePass.edgeThickness = 2.0; // 边框粗细
|
|
79
|
+
outlinePass.pulsePeriod = 2; // 呼吸闪烁周期 (0为不闪烁)
|
|
80
|
+
outlinePass.visibleEdgeColor.set('#ffffff'); // 可见边缘颜色
|
|
81
|
+
outlinePass.hiddenEdgeColor.set('#190a05'); // 被遮挡边缘颜色
|
|
82
|
+
|
|
83
|
+
composer.addPass(outlinePass);
|
|
84
|
+
|
|
85
|
+
// --- 4. 射线检测 (Raycaster) 与点击事件 ---
|
|
86
|
+
const raycaster = new THREE.Raycaster();
|
|
87
|
+
const mouse = new THREE.Vector2();
|
|
88
|
+
|
|
89
|
+
window.addEventListener('click', (event) => {
|
|
90
|
+
// 将鼠标位置归一化
|
|
91
|
+
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
|
92
|
+
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
|
93
|
+
|
|
94
|
+
raycaster.setFromCamera(mouse, camera);
|
|
95
|
+
|
|
96
|
+
// 检测交点
|
|
97
|
+
const intersects = raycaster.intersectObjects(scene.children);
|
|
98
|
+
|
|
99
|
+
if (intersects.length > 0) {
|
|
100
|
+
const selectedObject = intersects[0].object;
|
|
101
|
+
// 将选中的对象放入 OutlinePass 的选中列表
|
|
102
|
+
outlinePass.selectedObjects = [selectedObject];
|
|
103
|
+
} else {
|
|
104
|
+
// 点击空白处取消高亮
|
|
105
|
+
outlinePass.selectedObjects = [];
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// --- 5. 渲染循环 ---
|
|
110
|
+
function animate() {
|
|
111
|
+
requestAnimationFrame(animate);
|
|
112
|
+
controls.update();
|
|
113
|
+
// 注意:使用后期处理后,需调用 composer.render() 而非 renderer.render()
|
|
114
|
+
composer.render();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
window.addEventListener('resize', () => {
|
|
118
|
+
camera.aspect = window.innerWidth / window.innerHeight;
|
|
119
|
+
camera.updateProjectionMatrix();
|
|
120
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
121
|
+
composer.setSize(window.innerWidth, window.innerHeight);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
animate();
|
|
125
|
+
</script>
|
|
126
|
+
</body>
|
|
127
|
+
</html>
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div id="three-box"></div>
|
|
3
|
+
</template>
|
|
4
|
+
<script>
|
|
5
|
+
import CameraControls from 'camera-controls';
|
|
6
|
+
import { TilesRenderer } from '3d-tiles-renderer';
|
|
7
|
+
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
|
|
8
|
+
var instructions, renderer, cameraControls, scene, camera, renderTarget, tilesRenderer
|
|
9
|
+
var timeStamp = 0, animateId, singleFrameTime = 1 / 30, frameCount = 0
|
|
10
|
+
var needsRerender = false
|
|
11
|
+
var [fpsClock, timeStamp] = (function* (v) {
|
|
12
|
+
while (true) yield v;
|
|
13
|
+
})(0);
|
|
14
|
+
export default {
|
|
15
|
+
name: 'FlTiles',
|
|
16
|
+
data() {
|
|
17
|
+
return {
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
created() {
|
|
21
|
+
CameraControls.install({ THREE: this.THREE });
|
|
22
|
+
fpsClock = new this.THREE.Clock();
|
|
23
|
+
renderTarget = new this.THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
|
|
24
|
+
minFilter: this.THREE.LinearFilter,
|
|
25
|
+
magFilter: this.THREE.LinearFilter,
|
|
26
|
+
format: this.THREE.RGBAFormat,
|
|
27
|
+
stencilBuffer: true,
|
|
28
|
+
});
|
|
29
|
+
},
|
|
30
|
+
mounted() {
|
|
31
|
+
instructions = document.getElementById('three-box');
|
|
32
|
+
this.initRender();
|
|
33
|
+
this.initScene();
|
|
34
|
+
this.initCamera();
|
|
35
|
+
this.initControl();
|
|
36
|
+
this.initLight();
|
|
37
|
+
this.init3tiles();
|
|
38
|
+
cameraControls.addEventListener('wake', () => {
|
|
39
|
+
console.log(cameraControls._camera.position)
|
|
40
|
+
})
|
|
41
|
+
this.animate();
|
|
42
|
+
},
|
|
43
|
+
methods: {
|
|
44
|
+
initRender() {
|
|
45
|
+
renderer = new this.THREE.WebGLRenderer({
|
|
46
|
+
antialias: true,
|
|
47
|
+
logarithmicDepthBuffer: true,
|
|
48
|
+
});
|
|
49
|
+
renderer.setPixelRatio(window.devicePixelRatio * 2);
|
|
50
|
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
51
|
+
renderer.domElement.id = 'three-model';
|
|
52
|
+
renderer.shadowMap.enabled = true;
|
|
53
|
+
instructions.appendChild(renderer.domElement);
|
|
54
|
+
},
|
|
55
|
+
initScene() {
|
|
56
|
+
scene = new this.THREE.Scene();
|
|
57
|
+
scene.background = new this.THREE.Color(0xffffff)
|
|
58
|
+
},
|
|
59
|
+
initCamera() {
|
|
60
|
+
camera = new this.THREE.PerspectiveCamera(
|
|
61
|
+
45,
|
|
62
|
+
window.innerWidth / window.innerHeight,
|
|
63
|
+
0.1,
|
|
64
|
+
10000000
|
|
65
|
+
);
|
|
66
|
+
camera.position.set(
|
|
67
|
+
0,
|
|
68
|
+
10,
|
|
69
|
+
105.524230957031
|
|
70
|
+
);
|
|
71
|
+
},
|
|
72
|
+
initControl() {
|
|
73
|
+
// 初始化控件
|
|
74
|
+
cameraControls = new CameraControls(camera, renderer.domElement);
|
|
75
|
+
cameraControls.dollyToCursor = true;
|
|
76
|
+
cameraControls.smoothTime = 0.1;
|
|
77
|
+
cameraControls.draggingSmoothTime = 0.05;
|
|
78
|
+
cameraControls.truckSpeed = 2.0;
|
|
79
|
+
cameraControls.infinityDolly = true;
|
|
80
|
+
cameraControls.minDistance = 4;
|
|
81
|
+
},
|
|
82
|
+
// 初始化光源
|
|
83
|
+
initLight() {
|
|
84
|
+
const pmremGenerator = new this.THREE.PMREMGenerator(renderer);
|
|
85
|
+
scene.environment = pmremGenerator.fromScene(new RoomEnvironment(), 0.04).texture;
|
|
86
|
+
},
|
|
87
|
+
init3tiles() {
|
|
88
|
+
// 这个文件需要放到public文件夹下 Tile_+003_+003/Tile_+003_+003
|
|
89
|
+
tilesRenderer = new TilesRenderer('/tiles/tileset.json')
|
|
90
|
+
tilesRenderer.setCamera(camera);
|
|
91
|
+
tilesRenderer.setResolutionFromRenderer(camera, renderer)
|
|
92
|
+
scene.add(tilesRenderer.group);
|
|
93
|
+
|
|
94
|
+
setTimeout(() => (
|
|
95
|
+
needsRerender = true
|
|
96
|
+
), 1000)
|
|
97
|
+
},
|
|
98
|
+
// 添加一个标志,等待一帧让TilesRenderer初步解析JSON
|
|
99
|
+
debugTileset() {
|
|
100
|
+
if (tilesRenderer.root && frameCount++ < 10) { // 检查前几帧
|
|
101
|
+
console.log('[调试] TilesRenderer根节点:', tilesRenderer.root);
|
|
102
|
+
|
|
103
|
+
// 1. 尝试获取根节点的包围盒 (通常很大,是整个世界范围)
|
|
104
|
+
if (tilesRenderer.root.boundingVolume) {
|
|
105
|
+
console.log('[调试] 根节点包围盒:', tilesRenderer.root.boundingVolume);
|
|
106
|
+
// 如果是BOX类型,可以获取其中心
|
|
107
|
+
if (tilesRenderer.root.boundingVolume.box) {
|
|
108
|
+
const box = tilesRenderer.root.boundingVolume.box;
|
|
109
|
+
// box: [中心x, 中心y, 中心z, x轴半长, y轴半长, z轴半长]
|
|
110
|
+
console.log(`[调试] 根节点包围盒中心: [${box[0]}, ${box[1]}, ${box[2]}]`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 2. 检查第一个子节点(中间节点)
|
|
115
|
+
if (tilesRenderer.root.children && tilesRenderer.root.children[0]) {
|
|
116
|
+
const firstChild = tilesRenderer.root.children[0];
|
|
117
|
+
console.log('[调试] 第一个子节点:', firstChild);
|
|
118
|
+
if (firstChild.boundingVolume) {
|
|
119
|
+
console.log('[调试] 第一个子节点包围盒:', firstChild.boundingVolume);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// 3. 如果中间节点还有children,继续往下找
|
|
124
|
+
if (tilesRenderer.root.children &&
|
|
125
|
+
tilesRenderer.root.children[0] &&
|
|
126
|
+
tilesRenderer.root.children[0].children &&
|
|
127
|
+
tilesRenderer.root.children[0].children[0]) {
|
|
128
|
+
const deepestChild = tilesRenderer.root.children[0].children[0];
|
|
129
|
+
console.log('[调试] 更深层的子节点:', deepestChild);
|
|
130
|
+
if (deepestChild.content) {
|
|
131
|
+
console.log('[调试] 首个Content URI:', deepestChild.content.uri);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
animate() {
|
|
137
|
+
// const delta = fpsClock.getDelta();
|
|
138
|
+
// timeStamp += delta;
|
|
139
|
+
// requestAnimationFrame(this.animate)
|
|
140
|
+
// if (needsRerender) {
|
|
141
|
+
|
|
142
|
+
// needsRerender = false;
|
|
143
|
+
// let box = new this.THREE.Box3()
|
|
144
|
+
|
|
145
|
+
// if (tilesRenderer.getBoundingBox(box)) {
|
|
146
|
+
|
|
147
|
+
// box.getCenter(tilesRenderer.group.position);
|
|
148
|
+
|
|
149
|
+
// tilesRenderer.group.position.multiplyScalar(-1);
|
|
150
|
+
|
|
151
|
+
// }
|
|
152
|
+
|
|
153
|
+
// tilesRenderer.update();
|
|
154
|
+
|
|
155
|
+
// }
|
|
156
|
+
// cameraControls.update(timeStamp)
|
|
157
|
+
// tilesRenderer.update();
|
|
158
|
+
// renderer.render(scene, camera);
|
|
159
|
+
const delta = fpsClock.getDelta();
|
|
160
|
+
timeStamp += delta;
|
|
161
|
+
animateId = requestAnimationFrame(this.animate);
|
|
162
|
+
if (timeStamp > singleFrameTime) {
|
|
163
|
+
if (needsRerender) {
|
|
164
|
+
needsRerender = false
|
|
165
|
+
let box = new this.THREE.Box3()
|
|
166
|
+
if (tilesRenderer.getBoundingBox(box)) {
|
|
167
|
+
box.getCenter(tilesRenderer.group.position)
|
|
168
|
+
tilesRenderer.group.position.multiplyScalar(-1)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
cameraControls.update(timeStamp);
|
|
172
|
+
// this.debugTileset();
|
|
173
|
+
tilesRenderer.update();
|
|
174
|
+
renderer.render(scene, camera);
|
|
175
|
+
timeStamp = timeStamp % singleFrameTime;
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
</script>
|
|
181
|
+
<style lang="scss" scoped>
|
|
182
|
+
#three-box{
|
|
183
|
+
width: 100%;
|
|
184
|
+
height: 100%;
|
|
185
|
+
overflow: hidden;
|
|
186
|
+
}
|
|
187
|
+
</style>
|