You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

476 lines
12 KiB

<template>
<div class="model3d-view">
<el-space :gutter="10" class="toolbar">
<el-button type="primary" @click="test1">测试1</el-button>
<div class="demo-color-block">
<span class="demonstration">物体数:<el-text type="danger">{{ restate.objects }}</el-text></span>
<span class="demonstration"> 顶点数:<el-text type="danger">{{ restate.vertices }}</el-text></span>
<span class="demonstration"> 三角形数:<el-text type="danger">{{ restate.faces }}</el-text></span>
</div>
</el-space>
<div class="main-content">
<div class="canvas-container" ref="canvasContainer" />
</div>
</div>
</template>
<script setup lang="ts">
import * as THREE from 'three'
import { getCurrentInstance, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import Stats from 'three/examples/jsm/libs/stats.module'
import { LineGeometry } from 'three/examples/jsm/lines/LineGeometry'
import { LineMaterial } from 'three/examples/jsm/lines/LineMaterial'
import { Line2 } from 'three/examples/jsm/lines/Line2'
import MeasureRenderer from '@/modules/measure/MeasureRenderer.ts'
import { LineSegmentsGeometry } from 'three/examples/jsm/lines/LineSegmentsGeometry'
import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2'
const canvasContainer = ref(null)
let resizeObserver: ResizeObserver | null = null
let scene: THREE.Scene | null = null
let renderer: THREE.WebGLRenderer | null = null
let viewerDom: HTMLElement | null = null
let camera: THREE.PerspectiveCamera | THREE.OrthographicCamera | null = null
let controls: OrbitControls | null = null
let axesHelper: THREE.AxesHelper | null = null
let gridHelper: THREE.GridHelper | null = null
let statsControls: Stats | null = null
let animationFrameId: number | null = null
let modelGroup = new THREE.Group()
const pointMaterial = new THREE.SpriteMaterial({
color: 0xFFFF99, // 0x303133,
transparent: true,
side: THREE.DoubleSide,
opacity: 1,
sizeAttenuation: true
})
// const lineMaterial = new LineMaterial({
// color: 0xFF8C00,
// linewidth: 5,
// vertexColors: false,
// dashed: false
// })
const lineMaterial = new LineMaterial({
color: 0xFF8C00,
linewidth: 5
});
function test1() {
const xcount = 100
const zcount = 100
const dist = 1.25
const spacing = dist
const y = 0.1
const points = []
// 创建所有点
for (let z = 0; z < zcount; z++) {
for (let x = 0; x < xcount; x++) {
const px = x * dist
const pz = z * dist
const point = new THREE.Sprite(pointMaterial)
point.position.set(px, y, pz)
point.scale.set(0.25, 0.25, 1) // 设置大小
scene.add(point)
points.push(point.position.clone())
}
}
// // 绘制线段
// const lines = []
//
// // 绘制线段(每两个点一组)
// for (let z = 0; z < zcount; z++) {
// for (let x = 0; x < xcount - 1; x++) {
// const i = z * xcount + x
// drawLine(points[i], points[i + 1])
// }
// }
//
// for (let z = 0; z < zcount - 1; z++) {
// for (let x = 0; x < xcount; x++) {
// const i = z * xcount + x
// const belowIndex = (z + 1) * xcount + x
// drawLine(points[i], points[belowIndex])
// }
// }
const positions = [];
// 横向连接(右)
for (let z = 0; z < zcount; z++) {
for (let x = 0; x < xcount - 1; x++) {
const x1 = x * spacing;
const z1 = z * spacing;
const x2 = (x + 1) * spacing;
const z2 = z * spacing;
positions.push(x1, y, z1, x2, y, z2);
}
}
// 纵向连接(下)
for (let z = 0; z < zcount - 1; z++) {
for (let x = 0; x < xcount; x++) {
const x1 = x * spacing;
const z1 = z * spacing;
const x2 = x * spacing;
const z2 = (z + 1) * spacing;
positions.push(x1, y, z1, x2, y, z2);
}
}
const geometry = new LineSegmentsGeometry();
geometry.setPositions(positions); // [x1,y1,z1, x2,y2,z2, ...]
const material = new LineMaterial({
color: 0xFF8C00,
linewidth: 5,
vertexColors: false
});
const lineSegments = new LineSegments2(geometry, material);
lineSegments.computeLineDistances(); // 必须调用一次
lineSegments.name = 'grid-lines';
scene.add(lineSegments);
}
function drawLine(p1: THREE.Vector3, p2: THREE.Vector3) {
const geometry = new LineGeometry();
geometry.setPositions([p1.x, p1.y, p1.z, p2.x, p2.y, p2.z]);
// const material = new LineMaterial({
// color: 0xFF8C00,
// linewidth: 5
// });
const line = new Line2(geometry, lineMaterial);
line.computeLineDistances();
scene.add(line)
}
const restate = reactive({
targetColor: '#ff0000',
loadScale: 1,
mode: 'translate',
objects: 0,
vertices: 0,
faces: 0
})
// 状态变量
const state = {
showAxesHelper: true,
showGridHelper: true,
camera: {
position: { x: 0, y: 5, z: 10 },
rotation: { x: 0, y: 0, z: 0 }
}
}
onMounted(() => {
nextTick(() => {
initThree()
const viewerDom = canvasContainer.value
if (resizeObserver) {
resizeObserver.unobserve(viewerDom)
}
resizeObserver = new ResizeObserver(handleResize)
resizeObserver.observe(viewerDom)
window['cp'] = getCurrentInstance()
})
})
onBeforeUnmount(() => {
if (animationFrameId !== null) {
cancelAnimationFrame(animationFrameId)
animationFrameId = null
}
cleanupThree()
const viewerDom = canvasContainer.value
if (resizeObserver) {
resizeObserver.unobserve(viewerDom)
}
window['cp'] = null
})
function initThree() {
viewerDom = canvasContainer.value
if (!viewerDom) {
console.error('Viewer DOM element not found')
return
}
// 场景
scene = new THREE.Scene()
scene.background = new THREE.Color(0xeeeeee)
// 渲染器
renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
powerPreference: 'high-performance'
})
renderer.clearDepth()
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(viewerDom.clientWidth, viewerDom.clientHeight)
viewerDom.appendChild(renderer.domElement)
// 摄像机
// initMode3DCamera()
initMode2DCamera()
// 辅助线
axesHelper = new THREE.AxesHelper(5)
scene.add(axesHelper)
gridHelper = new THREE.GridHelper(1000, 1000)
scene.add(gridHelper)
gridHelper.visible = false
// 光照
const ambientLight = new THREE.AmbientLight(0xffffff, 1.5)
scene.add(ambientLight)
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5)
directionalLight.position.set(5, 5, 5).multiplyScalar(3)
directionalLight.castShadow = true
scene.add(directionalLight)
const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 1)
scene.add(hemisphereLight)
// 性能监控
statsControls = new Stats()
statsControls.showPanel(0)
statsControls.dom.style.right = '0'
statsControls.dom.style.left = 'auto'
viewerDom.appendChild(statsControls.dom)
animate()
}
// 动画循环
function animate() {
animationFrameId = requestAnimationFrame(animate)
renderView()
}
function handleResize(entries) {
for (let entry of entries) {
// entry.contentRect包含了元素的尺寸信息
console.log('Element size changed:', entry.contentRect)
const width = entry.contentRect.width
const height = entry.contentRect.height
if (camera instanceof THREE.PerspectiveCamera) {
camera.aspect = width / height
camera.updateProjectionMatrix()
} else if (camera instanceof THREE.OrthographicCamera) {
camera.left = width / -2
camera.right = width / 2
camera.top = height / 2
camera.bottom = height / -2
camera.updateProjectionMatrix()
}
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(width, height)
break
}
}
/**
* 初始化2D相机
*/
function initMode2DCamera() {
if (camera) {
scene.remove(camera)
}
// ============================ 创建正交相机
const cameraNew = new THREE.OrthographicCamera(
viewerDom.clientWidth / -2,
viewerDom.clientWidth / 2,
viewerDom.clientHeight / 2,
viewerDom.clientHeight / -2,
1,
500
)
cameraNew.position.set(0, 60, 0)
cameraNew.lookAt(0, 0, 0)
cameraNew.zoom = 60
camera = cameraNew
scene.add(camera)
// ============================ 创建控制器
const controlsNew = new OrbitControls(
camera,
renderer.domElement
)
controlsNew.enableDamping = false
controlsNew.enableZoom = true
controlsNew.enableRotate = false
controlsNew.mouseButtons = { LEFT: THREE.MOUSE.PAN, RIGHT: THREE.MOUSE.PAN } // 鼠标中键平移
controlsNew.screenSpacePanning = false // 定义平移时如何平移相机的位置 控制不上下移动
controlsNew.listenToKeyEvents(viewerDom) // 监听键盘事件
controlsNew.keys = { LEFT: 'KeyA', UP: 'KeyW', RIGHT: 'KeyD', BOTTOM: 'KeyS' }
controlsNew.panSpeed = 1
controlsNew.keyPanSpeed = 20 // normal 7
controlsNew.minDistance = 0.1
controlsNew.maxDistance = 1000
controls = controlsNew
cameraNew.updateProjectionMatrix()
}
function initMode3DCamera() {
if (camera) {
scene.remove(camera)
}
const viewerDom = canvasContainer.value
// ============================ 创建透视相机
const cameraNew = new THREE.PerspectiveCamera(25, viewerDom.clientWidth / viewerDom.clientHeight, 0.1, 2000)
cameraNew.position.set(5, 5, 5)
cameraNew.lookAt(0, 0, 0)
camera = cameraNew
scene.add(camera)
const controlsNew = new OrbitControls(camera, viewerDom)
controlsNew.mouseButtons = { LEFT: THREE.MOUSE.PAN, RIGHT: THREE.MOUSE.ROTATE } // 鼠标中键平移
controlsNew.enableDamping = false
controlsNew.screenSpacePanning = false // 定义平移时如何平移相机的位置 控制不上下移动
controlsNew.minDistance = 2
controlsNew.addEventListener('change', syncCameraState)
controls = controlsNew
controls.update()
camera.updateProjectionMatrix()
syncCameraState()
}
/**
* 重新加载相机状态到全局状态
*/
function syncCameraState() {
if (camera) {
state.camera.position.x = camera.position.x
state.camera.position.y = camera.position.y
state.camera.position.z = camera.position.z
state.camera.rotation.x = camera.rotation.x
state.camera.rotation.y = camera.rotation.y
state.camera.rotation.z = camera.rotation.z
}
}
function renderView() {
statsControls?.update()
renderer?.render(scene, camera)
}
function cleaupModel() {
if (modelGroup) {
scene.remove(modelGroup)
}
tcontrols.detach()
transformEditCtl.value.detach()
}
function cleanupThree() {
// 移除旧模型
if (scene) {
scene.traverse((obj: THREE.Mesh) => {
// 释放几何体
if (obj.geometry) {
obj.geometry.dispose()
}
// 释放材质
if (obj.material) {
if (Array.isArray(obj.material)) {
obj.material.forEach(m => m.dispose())
} else {
obj.material.dispose()
}
}
// 释放纹理
if (obj.texture) {
obj.texture.dispose()
}
// 释放渲染目标
if (obj.renderTarget) {
obj.renderTarget.dispose()
}
// 移除事件监听(如 OrbitControls)
if (obj.dispose) {
obj.dispose()
}
})
if (modelGroup) {
scene.remove(modelGroup)
}
// 清空场景
scene.children = []
modelGroup = null
}
if (statsControls) {
statsControls.dom.remove()
}
if (renderer) {
renderer.dispose()
renderer.forceContextLoss()
console.log('WebGL disposed, memory:', renderer.info.memory)
renderer.domElement = null
}
}
</script>
<style scoped lang="less">
.model3d-view {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
.main-content {
display: flex;
flex: 1;
overflow: hidden;
.model3d-content {
height: 100%;
display: flex;
flex-direction: row;
}
}
.canvas-container {
width: 100%;
height: 100%;
position: relative;
}
}
</style>