Browse Source

3D模型查看器

master
修宁 7 months ago
parent
commit
557cb420da
  1. 272
      src/designer/Model3DView.vue

272
src/designer/Model3DView.vue

@ -14,13 +14,14 @@
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref, onMounted, nextTick } from 'vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
import * as dat from 'three/examples/jsm/libs/lil-gui.module.min.js'
import Stats from 'three/examples/jsm/libs/stats.module'
// DOM refs
const canvasContainer = ref(null)
@ -28,6 +29,7 @@ const guiPanel = ref(null)
// Three.js
let scene, camera, renderer, controls
let statsControls, axesHelper, gridHelper
let modelGroup = new THREE.Group()
let gui
@ -36,40 +38,63 @@ const state = {
autoRotate: false,
showAxesHelper: true,
showGridHelper: true,
cameraPosition: [0, 5, 10]
cameraPosition: [0, 5, 10],
positionX: 0,
positionY: 0,
positionZ: 0,
rotationX: 0,
rotationY: 0,
rotationZ: 0,
scaleX: 1,
scaleY: 1,
scaleZ: 1,
camera: {
position: { x: 0, y: 5, z: 10 },
rotation: { x: 0, y: 0, z: 0 }
}
}
onMounted(() => {
const viewerDom = canvasContainer.value
nextTick(() => {
console.log('viewerDom', viewerDom)
initThree()
initGUI()
})
})
function initThree() {
const viewerDom = canvasContainer.value
//
scene = new THREE.Scene()
scene.background = new THREE.Color(0xeeeeee)
//
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / 2,
0.1,
1000
)
camera.position.set(...state.cameraPosition)
//
renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(canvasContainer.value.clientWidth, window.innerHeight * 0.8)
canvasContainer.value.appendChild(renderer.domElement)
renderer = new THREE.WebGLRenderer({
logarithmicDepthBuffer: true,
antialias: true,
alpha: true,
precision: 'mediump',
premultipliedAlpha: true,
preserveDrawingBuffer: false,
powerPreference: 'high-performance'
})
renderer.clearDepth()
renderer.shadowMap.enabled = true
renderer.outputColorSpace = THREE.SRGBColorSpace //
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(viewerDom.getBoundingClientRect().width, viewerDom.getBoundingClientRect().height)
viewerDom.appendChild(renderer.domElement)
//
controls = new OrbitControls(camera, renderer.domElement)
//
initMode3DCamera()
// 线
const axesHelper = new THREE.AxesHelper(5)
axesHelper = new THREE.AxesHelper(5)
scene.add(axesHelper)
const gridHelper = new THREE.GridHelper(20, 20)
gridHelper = new THREE.GridHelper(40, 40)
scene.add(gridHelper)
//
@ -79,52 +104,216 @@ function initThree() {
directionalLight.position.set(10, 10, 10)
scene.add(directionalLight)
//
statsControls = new Stats()
statsControls.dom.style.position = 'absolute'
viewerDom.appendChild(statsControls.dom)
//
function animate() {
requestAnimationFrame(animate)
if (state.autoRotate) {
modelGroup.rotation.y += 0.01
}
controls.update()
renderer.render(scene, camera)
renderView()
}
animate()
//
window.addEventListener('resize', () => {
camera.aspect = canvasContainer.value.clientWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(canvasContainer.value.clientWidth, window.innerHeight * 0.8)
})
// ResizeObserver
const resizeObserver = new ResizeObserver(handleResize)
// DOM
resizeObserver.observe(viewerDom)
}
function initGUI() {
const guiDom = guiPanel.value
gui = new dat.GUI({ autoPlace: false })
guiPanel.value.appendChild(gui.domElement)
guiDom.appendChild(gui.domElement)
//
gui.add(state, 'autoRotate').name('自动旋转')
// 线
gui.add(state, 'showAxesHelper').name('显示坐标轴').onChange(val => {
scene.getObjectByName('AxesHelper').visible = val
axesHelper.visible = val
})
gui.add(state, 'showGridHelper').name('显示网格').onChange(val => {
scene.getObjectByName('GridHelper').visible = val
gridHelper.visible = val
})
//
const cameraFolder = gui.addFolder('相机位置')
cameraFolder.add(state.cameraPosition, 0, -10, 10).name('X')
cameraFolder.add(state.cameraPosition, 1, -10, 10).name('Y')
cameraFolder.add(state.cameraPosition, 2, 5, 30).name('Z').onChange(() => {
camera.position.set(...state.cameraPosition)
// Position
const cameraPosFolder = gui.addFolder('Camera Position')
cameraPosFolder.add(state.camera.position, 'x', -100, 100).step(0.1).listen().onChange(val => {
camera.position.x = val
})
cameraPosFolder.add(state.camera.position, 'y', -100, 100).step(0.1).listen().onChange(val => {
camera.position.y = val
})
cameraPosFolder.add(state.camera.position, 'z', -100, 100).step(0.1).listen().onChange(val => {
camera.position.z = val
})
// Rotation (in radians)
const cameraRotationFolder = gui.addFolder('Camera Rotation')
cameraRotationFolder.add(state.camera.rotation, 'x', -Math.PI, Math.PI).listen().onChange(val => {
camera.rotation.x = val
})
cameraRotationFolder.add(state.camera.rotation, 'y', -Math.PI, Math.PI).listen().onChange(val => {
camera.rotation.y = val
})
cameraFolder.open()
cameraRotationFolder.add(state.camera.rotation, 'z', -Math.PI, Math.PI).listen().onChange(val => {
camera.rotation.z = val
})
// Position
const positionFolder = gui.addFolder('Position')
positionFolder.add(state, 'positionX', -10, 10).onChange(val => {
modelGroup.position.x = val
})
positionFolder.add(state, 'positionY', -10, 10).onChange(val => {
modelGroup.position.y = val
})
positionFolder.add(state, 'positionZ', -10, 10).onChange(val => {
modelGroup.position.z = val
})
// Rotation (in radians)
const rotationFolder = gui.addFolder('Rotation')
rotationFolder.add(state, 'rotationX', -Math.PI, Math.PI).onChange(val => {
modelGroup.rotation.x = val
})
rotationFolder.add(state, 'rotationY', -Math.PI, Math.PI).onChange(val => {
modelGroup.rotation.y = val
})
rotationFolder.add(state, 'rotationZ', -Math.PI, Math.PI).onChange(val => {
modelGroup.rotation.z = val
})
// Scale
const scaleFolder = gui.addFolder('Scale')
scaleFolder.add(state, 'scaleX', 0.001, 10).onChange(val => {
modelGroup.scale.x = val
})
scaleFolder.add(state, 'scaleY', 0.001, 10).onChange(val => {
modelGroup.scale.y = val
})
scaleFolder.add(state, 'scaleZ', 0.001, 10).onChange(val => {
modelGroup.scale.z = val
})
//
// const cameraFolder = gui.addFolder('')
// cameraFolder.add(state.cameraPosition, 0, -10, 10).name('X')
// cameraFolder.add(state.cameraPosition, 1, -10, 10).name('Y')
// cameraFolder.add(state.cameraPosition, 2, 5, 30).name('Z').onChange(() => {
// camera.position.set(...state.cameraPosition)
// })
// cameraFolder.open()
}
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.setSize(width, height)
break
}
}
function initMode2DCamera() {
// const controlsNew = new OrbitControls(
// cameraNew,
// renderer?.domElement
// )
// controlsNew.enableDamping = false
// controlsNew.enableZoom = true
// controlsNew.enableRotate = false
// controlsNew.mouseButtons = { LEFT: THREE.MOUSE.PAN, RIGHT: THREE.MOUSE.ROTATE } //
// controlsNew.screenSpacePanning = false //
// controlsNew.keys = { LEFT: 'KeyA', UP: 'KeyW', RIGHT: 'KeyD', BOTTOM: 'KeyS' }
// controlsNew.listenToKeyEvents(viewerDom) //
// controlsNew.panSpeed = 1
// controlsNew.keyPanSpeed = 20 // normal 7
// controlsNew.minDistance = 0.1
// controlsNew.maxDistance = 1000
}
function initMode3DCamera() {
if (camera) {
scene.remove(camera)
}
const viewerDom = canvasContainer.value
//
const cameraNew = new THREE.PerspectiveCamera(25, viewerDom.clientWidth / viewerDom.clientHeight, 1, 2000)
//
// cameraNew.position.set(4, 2, -3);
cameraNew.position.set(30, 30, 30)
//
cameraNew.lookAt(0, 0, 0)
camera = cameraNew
scene.add(camera)
const controlsNew = new OrbitControls(
camera,
renderer?.domElement
)
controlsNew.mouseButtons = { LEFT: THREE.MOUSE.PAN, RIGHT: THREE.MOUSE.ROTATE } //
controlsNew.enableDamping = false
controlsNew.screenSpacePanning = false //
controlsNew.minDistance = 2
controls = controlsNew
camera.updateProjectionMatrix()
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)
if (camera) {
// cameraTransform
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 handleFileUpload(event) {
const file = event.target.files[0]
if (!file) return
@ -158,9 +347,18 @@ function handleFileUpload(event) {
} else {
alert('不支持的文件类型!')
}
// modelGroup.position.set()
// modelGroup.rotation.set(0, 0, 0)
// modelGroup.scale.set(1, 1, 1)
}
</script>
<style scoped>
.model3d-view {
display: flex;
flex-direction: column;
flex-grow: 1;
.dialog-container {
display: flex;
flex-direction: column;
@ -191,4 +389,6 @@ function handleFileUpload(event) {
padding: 10px;
overflow-y: auto;
}
}
</style>
Loading…
Cancel
Save