5 changed files with 0 additions and 538 deletions
@ -1,357 +0,0 @@ |
|||||
<template> |
|
||||
<div class="model3d-view"> |
|
||||
<el-space :gutter="10" class="toolbar"> |
|
||||
<el-upload :on-change="handleFileChange" |
|
||||
:show-file-list="false" accept=".fbx,.obj,.mtl,.3ds" action="" :auto-upload="false"> |
|
||||
<el-button type="primary">打开模型</el-button> |
|
||||
</el-upload> |
|
||||
<el-upload :on-change="handleTextureUpload" |
|
||||
:show-file-list="false" accept=".png,.jpg,.jpeg" action="" :auto-upload="false" |
|
||||
list-type="picture"> |
|
||||
<el-button>打开贴图</el-button> |
|
||||
</el-upload> |
|
||||
<el-upload :on-change="handleMtlUpload" |
|
||||
:show-file-list="false" accept=".mtl" action="" :auto-upload="false" |
|
||||
list-type="picture"> |
|
||||
<el-button>打开材质</el-button> |
|
||||
</el-upload> |
|
||||
<div class="demo-color-block"> |
|
||||
<span class="demonstration">材质颜色</span> |
|
||||
<el-color-picker v-model="restate.targetColor" /> |
|
||||
</div> |
|
||||
<!-- 按 1:1 / 1:0.001 比例 --> |
|
||||
<div class="demo-color-block"> |
|
||||
<span class="demonstration">默认比例</span> |
|
||||
<el-radio-group v-model="restate.loadScale"> |
|
||||
<el-radio-button label="1" :value="1" /> |
|
||||
<el-radio-button label="0.01" :value="0.01" /> |
|
||||
<el-radio-button label="0.001" :value="0.001" /> |
|
||||
</el-radio-group> |
|
||||
</div> |
|
||||
<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"> |
|
||||
<Split class="model3d-content" :direction="'horizontal'"> |
|
||||
<SplitArea class="model3d-canvas" :size="70"> |
|
||||
<!-- Three.js 渲染画布 --> |
|
||||
<div class="canvas-container" ref="canvasContainer"> |
|
||||
<canvas ref="canvasRef"></canvas> |
|
||||
<div class="canvas-left-toolbar"> |
|
||||
<button class="Button" :class="{selected:(restate.mode==='translate')}" title="平移" |
|
||||
@mousedown="restate.mode='translate'"> |
|
||||
<component :is="renderIcon('element Rank')" /> |
|
||||
</button> |
|
||||
<button class="Button" :class="{selected:(restate.mode==='rotate')}" title="旋转" |
|
||||
@mousedown="restate.mode='rotate'"> |
|
||||
<component :is="renderIcon('element RefreshLeft')" /> |
|
||||
</button> |
|
||||
<button class="Button" :class="{selected:(restate.mode==='scale')}" title="缩放" |
|
||||
@mousedown="restate.mode='scale'"> |
|
||||
<component :is="renderIcon('element ScaleToOriginal')" /> |
|
||||
</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</SplitArea> |
|
||||
<SplitArea class="model3d-gui" :size="30"> |
|
||||
<div class="model3d-gui-wrap"> |
|
||||
<!-- 右侧面板 --> |
|
||||
<div class="gui-toolbar"> |
|
||||
<TransformEdit ref="transformEditCtl" /> |
|
||||
</div> |
|
||||
<div class="gui-panel" ref="guiPanel"></div> |
|
||||
</div> |
|
||||
</SplitArea> |
|
||||
</Split> |
|
||||
</div> |
|
||||
</div> |
|
||||
</template> |
|
||||
<script setup lang="ts"> |
|
||||
import TransformEdit from '@/components/propertyEdit/TransformEdit.vue' |
|
||||
import { ref, onMounted, nextTick, reactive, watch, getCurrentInstance, onUnmounted, onBeforeUnmount } from 'vue' |
|
||||
import '@babylonjs/core/Debug/debugLayer' |
|
||||
import '@babylonjs/inspector' |
|
||||
import { |
|
||||
Engine, |
|
||||
Scene, |
|
||||
ArcRotateCamera, |
|
||||
Vector3, |
|
||||
HemisphericLight, |
|
||||
MeshBuilder, |
|
||||
Color4, |
|
||||
Camera, |
|
||||
GizmoManager, |
|
||||
DirectionalLight |
|
||||
} from '@babylonjs/core' |
|
||||
import { TransformNode } from '@babylonjs/core' |
|
||||
import Split from '@/components/split/split.vue' |
|
||||
import SplitArea from '@/components/split/split-area.vue' |
|
||||
import { renderIcon } from '@/utils/webutils.js' |
|
||||
import { GridMaterial } from '@babylonjs/materials' |
|
||||
|
|
||||
// DOM refs |
|
||||
const canvasRef = ref(null) |
|
||||
|
|
||||
// Three.js 场景相关 |
|
||||
let engine: Engine |
|
||||
let scene: Scene |
|
||||
let camera: ArcRotateCamera |
|
||||
let gizmoManager: GizmoManager |
|
||||
let currentMesh = null |
|
||||
let modelGroup, resizeObserver |
|
||||
|
|
||||
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(() => { |
|
||||
initBabylon() |
|
||||
|
|
||||
const viewerDom = canvasRef.value |
|
||||
resizeObserver = new ResizeObserver(handleResize) |
|
||||
resizeObserver.observe(viewerDom) |
|
||||
handleResize() |
|
||||
|
|
||||
window['model3dView'] = getCurrentInstance() |
|
||||
window['engine'] = engine |
|
||||
window['scene'] = scene |
|
||||
window['camera'] = camera |
|
||||
window['gizmoManager'] = gizmoManager |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
onBeforeUnmount(() => { |
|
||||
cleanupThree() |
|
||||
|
|
||||
const viewerDom = canvasRef.value |
|
||||
if (resizeObserver) { |
|
||||
resizeObserver.unobserve(viewerDom) |
|
||||
} |
|
||||
|
|
||||
window['model3dView'] = null |
|
||||
window['model3dViewRenderer'] = null |
|
||||
}) |
|
||||
|
|
||||
watch(() => restate.targetColor, (newVal) => { |
|
||||
// if (modelGroup) { |
|
||||
// modelGroup.traverse((child) => { |
|
||||
// if (child.isMesh && child.material?.color) { |
|
||||
// child.material.color.set(newVal) |
|
||||
// child.material.needsUpdate = true |
|
||||
// } |
|
||||
// }) |
|
||||
// } |
|
||||
}) |
|
||||
|
|
||||
watch(() => restate.mode, (newVal) => { |
|
||||
// if (tcontrols) { |
|
||||
// tcontrols.setMode(newVal) |
|
||||
// } |
|
||||
}) |
|
||||
|
|
||||
function initBabylon() { |
|
||||
// 渲染器 |
|
||||
engine = new Engine(canvasRef.value, true) |
|
||||
scene = new Scene(engine) |
|
||||
|
|
||||
// 创建辅助线 |
|
||||
const ground = MeshBuilder.CreateGround('ground', { width: 25, height: 25 }) |
|
||||
ground.material = new GridMaterial('groundMat') |
|
||||
ground.material.backFaceCulling = false |
|
||||
|
|
||||
camera = new ArcRotateCamera('ArcRotateCamera', 1, 0.8, 10, new Vector3(0, 0, 0), scene) |
|
||||
// camera.panningInertia = 0 |
|
||||
camera.attachControl(true, true, 0) |
|
||||
camera.lowerRadiusLimit = 2 //相机缩小半径上限 限制相机距离焦点的距离 |
|
||||
camera.upperRadiusLimit = 10 //相机放大半径上限 upperRadiusLimit的值不应小于lowerRadiusLimit,避免出现错误或不起作用。 |
|
||||
camera.wheelDeltaPercentage = 0.09 //鼠标滚轮灵敏度 |
|
||||
camera.checkCollisions = true // 开启视角和场景物体的碰撞 |
|
||||
camera.upperBetaLimit = (Math.PI / 2) * 0.9 // 视角最大beta角度 |
|
||||
camera.lowerRadiusLimit = 0.1 // 视角最小距离 |
|
||||
camera.upperRadiusLimit = 1000 // 视角最大距离 |
|
||||
camera.radius = 1 // 初始化视角距离 |
|
||||
camera.setTarget(Vector3.Zero()) // 设置视角中心 |
|
||||
|
|
||||
// camera.panningSensibility = 1 // 相机对运动和旋转的灵敏度 |
|
||||
|
|
||||
// 初始化光照 |
|
||||
new HemisphericLight('light1', new Vector3(1, 1, 0), scene) |
|
||||
const directionalLight = new DirectionalLight( |
|
||||
'dirLight', |
|
||||
new Vector3(-1, -1, -1), |
|
||||
scene |
|
||||
) |
|
||||
|
|
||||
// 初始化 Gizmo |
|
||||
gizmoManager = new GizmoManager(scene) |
|
||||
gizmoManager.usePointerToAttachGizmos = false |
|
||||
|
|
||||
// // 创建几何体和材质做测试用 |
|
||||
const box = MeshBuilder.CreateBox('box', {}) |
|
||||
box.position = new Vector3(1, 2, 5) |
|
||||
|
|
||||
engine.runRenderLoop(() => { |
|
||||
scene.render() |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/** |
|
||||
* 重新加载相机状态到全局状态 |
|
||||
*/ |
|
||||
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 handleTextureUpload(file) { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
let lastObjfile = undefined |
|
||||
|
|
||||
function handleMtlUpload(file) { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
function addGroupToScene(group) { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
function handleFileChange(file) { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
|
|
||||
function cleaupModel() { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
function cleanupThree() { |
|
||||
// 移除旧模型 |
|
||||
|
|
||||
} |
|
||||
|
|
||||
function handleResize() { |
|
||||
if (canvasRef.value) { |
|
||||
const width = canvasRef.value.clientWidth |
|
||||
const height = canvasRef.value.clientHeight |
|
||||
if (engine) { |
|
||||
engine.resize() |
|
||||
engine.setSize(width, height) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
</script> |
|
||||
<style scoped lang="less"> |
|
||||
.model3d-view { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
flex-grow: 1; |
|
||||
overflow: hidden; |
|
||||
|
|
||||
.canvas-left-toolbar { |
|
||||
position: absolute; |
|
||||
left: 5px; |
|
||||
top: 5px; |
|
||||
width: 32px; |
|
||||
background: #eee; |
|
||||
text-align: center; |
|
||||
|
|
||||
button { |
|
||||
color: #555; |
|
||||
background-color: #ddd; |
|
||||
border: 0; |
|
||||
margin: 0; |
|
||||
padding: 5px 8px; |
|
||||
font-size: 12px; |
|
||||
text-transform: uppercase; |
|
||||
cursor: pointer; |
|
||||
outline: none; |
|
||||
} |
|
||||
|
|
||||
button.selected { |
|
||||
background-color: #fff; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.toolbar { |
|
||||
padding: 10px; |
|
||||
background: #f5f5f5; |
|
||||
border-bottom: 1px solid #ccc; |
|
||||
} |
|
||||
|
|
||||
.dialog-container { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
height: 100vh; |
|
||||
} |
|
||||
|
|
||||
.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; |
|
||||
overflow: hidden; |
|
||||
|
|
||||
canvas { |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
touch-action: none; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.model3d-gui-wrap { |
|
||||
border-left: 1px solid #ccc; |
|
||||
background: #111; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
|
|
||||
.gui-panel { |
|
||||
:deep(.lil-gui.root) { |
|
||||
width: 100%; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
</style> |
|
||||
Loading…
Reference in new issue