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.
701 lines
18 KiB
701 lines
18 KiB
<template>
|
|
<div class="model3d-view">
|
|
<el-space :gutter="10" class="toolbar">
|
|
<el-button type="primary" @click="test1">测试1</el-button>
|
|
<el-button type="primary" @click="test2">测试2</el-button>
|
|
<el-button type="primary" @click="test3">测试3</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>
|
|
<span class="demonstration"> 标签:<el-text type="danger">{{ restate.viewLabelCount }}</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'
|
|
import { Text } from 'troika-three-text'
|
|
import SimSunTTF from '@/assets/fonts/simsunb.ttf'
|
|
|
|
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
|
|
})
|
|
|
|
/**
|
|
* drawLine2
|
|
*/
|
|
function test1() {
|
|
cleanupThree()
|
|
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())
|
|
}
|
|
}
|
|
|
|
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 line = new Line2(geometry, lineMaterial)
|
|
line.computeLineDistances()
|
|
|
|
scene.add(line)
|
|
}
|
|
|
|
// 绘制线段(每两个点一组)
|
|
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])
|
|
}
|
|
}
|
|
refreshCount()
|
|
}
|
|
|
|
/**
|
|
* LineSegments2
|
|
*/
|
|
function test2() {
|
|
cleanupThree()
|
|
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 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)
|
|
|
|
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)
|
|
refreshCount()
|
|
}
|
|
|
|
function isLabelInView(label, frustum) {
|
|
const pos = new THREE.Vector3()
|
|
label.getWorldPosition(pos)
|
|
|
|
// 正交相机的视锥体范围
|
|
if (frustum.containsPoint(pos)) {
|
|
// 检查标签是否在相机位置的最大距离内
|
|
if (shouldShowLabel(label)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
const labels: Text[] = []
|
|
|
|
function getLabelPixelSize(fontSize, cameraZoom) {
|
|
const pixelRatio = renderer.getPixelRatio()
|
|
const referenceZoom = 1
|
|
const referenceFontSize = 0.2
|
|
const referencePixelSize = 16 // fontSize=0.2, zoom=1 时显示为 16px
|
|
|
|
const scale = (fontSize / referenceFontSize) * (cameraZoom / referenceZoom)
|
|
return referencePixelSize * scale * pixelRatio
|
|
}
|
|
|
|
function shouldShowLabel(label, minPixelSize = 700) {
|
|
const pixelSize = getLabelPixelSize(label.fontSize, camera.zoom)
|
|
return pixelSize >= minPixelSize
|
|
}
|
|
|
|
/**
|
|
* InstanceMesh(Point) + BufferGeometry + Label
|
|
*/
|
|
function test3() {
|
|
cleanupThree() // 清空画布
|
|
const xcount = 300
|
|
const zcount = 100
|
|
const dist = 1.25
|
|
const spacing = dist
|
|
const y = 0.1
|
|
|
|
const noShaderMaterial = new THREE.MeshBasicMaterial({
|
|
color: 0xFFFF99,
|
|
transparent: true,
|
|
depthWrite: false,
|
|
side: THREE.DoubleSide
|
|
})
|
|
const planeGeometry = new THREE.PlaneGeometry(0.25, 0.25)
|
|
|
|
// 使用 InstancedMesh 网格优化
|
|
const instancedMesh = new THREE.InstancedMesh(planeGeometry, noShaderMaterial, zcount * xcount)
|
|
instancedMesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage)
|
|
const dummy = new THREE.Object3D()
|
|
|
|
const points = []
|
|
|
|
// 创建所有点
|
|
for (let z = 0; z < zcount; z++) {
|
|
for (let x = 0; x < xcount; x++) {
|
|
const px = x * dist
|
|
const pz = z * dist
|
|
|
|
dummy.position.set(px, y, pz)
|
|
dummy.rotation.set(-Math.PI / 2, 0, 0)
|
|
dummy.updateMatrix()
|
|
instancedMesh.setMatrixAt(z * xcount + x, dummy.matrix)
|
|
|
|
points.push(new THREE.Vector3(px, y, pz))
|
|
}
|
|
}
|
|
scene.add(instancedMesh)
|
|
|
|
const positions = []
|
|
labels.length = 0
|
|
|
|
function createTextLabel(text, position): Text {
|
|
const label = new Text()
|
|
label.text = text
|
|
label.font = SimSunTTF
|
|
label.fontSize = 0.2
|
|
label.color = '#333333'
|
|
label.opacity = 0.8
|
|
label.padding = 0.2
|
|
label.anchorX = 'center'
|
|
label.anchorY = 'middle'
|
|
label.depthOffset = 1
|
|
label.backgroundColor = '#000000'
|
|
label.backgroundOpacity = 0.6
|
|
label.material.depthTest = false
|
|
label.position.copy(position)
|
|
label.name = MeasureRenderer.LABEL_NAME
|
|
|
|
label.position.set(position.x, position.y + 0.3, position.z - 0.2)
|
|
label.quaternion.copy(camera.quaternion)
|
|
label.visible = false
|
|
// label.sync()
|
|
|
|
return label
|
|
}
|
|
|
|
// 横向连接(右)
|
|
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)
|
|
|
|
// 计算中点和长度
|
|
const midPoint = new THREE.Vector3((x1 + x2) / 2, y + 0.5, (z1 + z2) / 2)
|
|
const length = Math.hypot(x2 - x1, y - y, z2 - z1)
|
|
const label = createTextLabel(length.toFixed(2) + 'm', midPoint)
|
|
labels.push(label)
|
|
}
|
|
}
|
|
|
|
// 纵向连接(下)
|
|
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 midPoint = new THREE.Vector3((x1 + x2) / 2, y + 0.5, (z1 + z2) / 2)
|
|
const length = Math.hypot(x2 - x1, y - y, z2 - z1)
|
|
const label = createTextLabel(length.toFixed(2) + 'm', midPoint)
|
|
labels.push(label)
|
|
}
|
|
}
|
|
|
|
const positionNums = new Float32Array(positions)
|
|
const geometry = new THREE.BufferGeometry()
|
|
geometry.setAttribute('position', new THREE.BufferAttribute(positionNums, 3))
|
|
const material = new THREE.LineBasicMaterial({ color: 0xFF8C00 })
|
|
const lineSegments = new THREE.LineSegments(geometry, material)
|
|
lineSegments.name = 'grid-lines'
|
|
scene.add(lineSegments)
|
|
|
|
labels.forEach(label => scene.add(label))
|
|
|
|
// 统计总数量
|
|
refreshCount()
|
|
}
|
|
|
|
|
|
const restate = reactive({
|
|
targetColor: '#ff0000',
|
|
loadScale: 1,
|
|
viewLabelCount: 0,
|
|
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)
|
|
|
|
renderer.setAnimationLoop(animate)
|
|
renderer.setClearColor(0x000000, 0)
|
|
|
|
// animate()
|
|
}
|
|
|
|
// 动画循环
|
|
let frameCount = 0
|
|
|
|
function animate() {
|
|
// animationFrameId = requestAnimationFrame(animate)
|
|
renderView()
|
|
|
|
if (frameCount++ % 60 === 0) { // 每 60 帧更新一次文本
|
|
const frustum = new THREE.Frustum()
|
|
const cameraCopy = camera.clone()
|
|
|
|
// 必须更新相机的世界矩阵
|
|
cameraCopy.updateMatrixWorld()
|
|
|
|
// 构造投影矩阵
|
|
const projScreenMatrix = new THREE.Matrix4().multiplyMatrices(
|
|
cameraCopy.projectionMatrix,
|
|
cameraCopy.matrixWorldInverse
|
|
)
|
|
|
|
// 设置视锥体
|
|
frustum.setFromProjectionMatrix(projScreenMatrix)
|
|
|
|
let viewLabelCount = 0
|
|
labels.forEach((label: Text) => {
|
|
// label.quaternion.copy(camera.quaternion) // billboard 效果保持朝向相机
|
|
const isvis = isLabelInView(label, frustum, camera.position)
|
|
if (isvis) {
|
|
viewLabelCount++
|
|
}
|
|
if (isvis && label.visible === false) {
|
|
label.visible = true
|
|
label.sync()
|
|
} else if (!isvis && label.visible === true) {
|
|
label.visible = false
|
|
label.sync()
|
|
}
|
|
})
|
|
restate.viewLabelCount = viewLabelCount
|
|
}
|
|
}
|
|
|
|
function handleResize(entries) {
|
|
for (let entry of entries) {
|
|
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 refreshCount() {
|
|
// 遍历场景中的所有对象
|
|
let totalObjects = 0
|
|
let totalVertices = 0
|
|
let totalFaces = 0
|
|
scene.traverse(function(child) {
|
|
if (child.isMesh) {
|
|
totalObjects++
|
|
|
|
// 获取几何体
|
|
const geometry = child.geometry
|
|
|
|
// 如果几何体是 BufferGeometry 类型
|
|
if (geometry.isBufferGeometry) {
|
|
// 计算顶点数
|
|
if (geometry.attributes.position) {
|
|
totalVertices += geometry.attributes.position.count
|
|
}
|
|
|
|
// 计算面数(假设每个面都是由三个顶点组成的三角形)
|
|
if (geometry.index) {
|
|
totalFaces += geometry.index.count / 3
|
|
} else if (geometry.attributes.position) {
|
|
// 如果没有索引,计算非索引几何体的面数
|
|
totalFaces += geometry.attributes.position.count / 3
|
|
}
|
|
}
|
|
// 如果几何体是 Geometry 类型(较旧的版本使用)
|
|
else if (geometry.isGeometry) {
|
|
// 计算顶点数
|
|
totalVertices += geometry.vertices.length
|
|
|
|
// 计算面数
|
|
totalFaces += geometry.faces.length
|
|
}
|
|
}
|
|
})
|
|
restate.objects = totalObjects
|
|
restate.vertices = totalVertices
|
|
restate.faces = totalFaces
|
|
}
|
|
|
|
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()
|
|
}
|
|
})
|
|
|
|
// 清空场景
|
|
scene.children = []
|
|
modelGroup = 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>
|
|
|