|
|
@ -3,59 +3,58 @@ |
|
|
<el-space :gutter="10" class="toolbar"> |
|
|
<el-space :gutter="10" class="toolbar"> |
|
|
<el-upload :on-change="handleFileChange" |
|
|
<el-upload :on-change="handleFileChange" |
|
|
:show-file-list="false" accept=".fbx,.obj,.mtl,.3ds" action="" :auto-upload="false"> |
|
|
:show-file-list="false" accept=".fbx,.obj,.mtl,.3ds" action="" :auto-upload="false"> |
|
|
<el-button type="primary">上传文件</el-button> |
|
|
<el-button type="primary">打开模型</el-button> |
|
|
</el-upload> |
|
|
</el-upload> |
|
|
<el-upload :on-change="handleTextureUpload" |
|
|
<el-upload :on-change="handleTextureUpload" |
|
|
:show-file-list="false" accept=".png,.jpg,.jpeg" action="" :auto-upload="false" |
|
|
:show-file-list="false" accept=".png,.jpg,.jpeg" action="" :auto-upload="false" |
|
|
list-type="picture"> |
|
|
list-type="picture"> |
|
|
<el-button>上传贴图</el-button> |
|
|
<el-button>打开贴图</el-button> |
|
|
</el-upload> |
|
|
</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 比例 --> |
|
|
<!-- 按 1:1 / 1:0.001 比例 --> |
|
|
<el-radio-group v-model="restate.loadScale" size="small"> |
|
|
<div class="demo-color-block"> |
|
|
<el-radio-button label="1:1" :value="1" /> |
|
|
<span class="demonstration">默认比例</span> |
|
|
<el-radio-button label="1:0.001" :value="0.001" /> |
|
|
<el-radio-group v-model="restate.loadScale"> |
|
|
</el-radio-group> |
|
|
<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> |
|
|
</el-space> |
|
|
</el-space> |
|
|
<div class="main-content"> |
|
|
<div class="main-content"> |
|
|
<Split class="model3d-content" :direction="'horizontal'"> |
|
|
<Split class="model3d-content" :direction="'horizontal'"> |
|
|
<SplitArea class="model3d-canvas" :size="70"> |
|
|
<SplitArea class="model3d-canvas" :size="70"> |
|
|
<!-- Three.js 渲染画布 --> |
|
|
<!-- Three.js 渲染画布 --> |
|
|
<div class="canvas-container" ref="canvasContainer"></div> |
|
|
<div class="canvas-container" ref="canvasContainer"> |
|
|
|
|
|
<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> |
|
|
<SplitArea class="model3d-gui" :size="30"> |
|
|
<SplitArea class="model3d-gui" :size="30"> |
|
|
<div class="model3d-gui-wrap"> |
|
|
<div class="model3d-gui-wrap"> |
|
|
<!-- 右侧面板 --> |
|
|
<!-- 右侧面板 --> |
|
|
<div class="gui-toolbar"> |
|
|
<div class="gui-toolbar"> |
|
|
<div class="gui-row"> |
|
|
<TransformEdit ref="transformEditCtl" /> |
|
|
<div class="gui-item-name"></div> |
|
|
|
|
|
<div class="gui-item">X</div> |
|
|
|
|
|
<div class="gui-item">Y</div> |
|
|
|
|
|
<div class="gui-item">Z</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="gui-row"> |
|
|
|
|
|
<div class="gui-item-name"> |
|
|
|
|
|
<component :is="renderIcon('element Aim')"></component> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div> |
|
|
|
|
|
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div> |
|
|
|
|
|
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="gui-row"> |
|
|
|
|
|
<div class="gui-item-name"> |
|
|
|
|
|
<component :is="renderIcon('antd RotateLeftOutlined')"></component> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div> |
|
|
|
|
|
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div> |
|
|
|
|
|
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="gui-row"> |
|
|
|
|
|
<div class="gui-item-name"> |
|
|
|
|
|
<component :is="renderIcon('element ScaleToOriginal')"></component> |
|
|
|
|
|
</div> |
|
|
|
|
|
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div> |
|
|
|
|
|
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div> |
|
|
|
|
|
<div class="gui-item"><el-input-number v-model="restate.num" size="small" :min="1" :max="10" :controls="false"/></div> |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
<div class="gui-panel" ref="guiPanel"></div> |
|
|
<div class="gui-panel" ref="guiPanel"></div> |
|
|
</div> |
|
|
</div> |
|
|
@ -65,22 +64,25 @@ |
|
|
</div> |
|
|
</div> |
|
|
</template> |
|
|
</template> |
|
|
<script setup> |
|
|
<script setup> |
|
|
import { renderIcon } from '@/utils/webutils.js' |
|
|
import TransformEdit from '@/components/propertyEdit/TransformEdit.vue' |
|
|
import { ref, onMounted, nextTick, reactive } from 'vue' |
|
|
import { ref, onMounted, nextTick, reactive, watch } from 'vue' |
|
|
import * as THREE from 'three' |
|
|
import * as THREE from 'three' |
|
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' |
|
|
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' |
|
|
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader' |
|
|
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader' |
|
|
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' |
|
|
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' |
|
|
|
|
|
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js' |
|
|
import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader' |
|
|
import { TDSLoader } from 'three/examples/jsm/loaders/TDSLoader' |
|
|
import * as dat from 'three/examples/jsm/libs/lil-gui.module.min.js' |
|
|
import * as dat from 'three/examples/jsm/libs/lil-gui.module.min.js' |
|
|
import Stats from 'three/examples/jsm/libs/stats.module' |
|
|
import Stats from 'three/examples/jsm/libs/stats.module' |
|
|
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js' |
|
|
import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js' |
|
|
import Split from '@/components/split/split.vue' |
|
|
import Split from '@/components/split/split.vue' |
|
|
import SplitArea from '@/components/split/split-area.vue' |
|
|
import SplitArea from '@/components/split/split-area.vue' |
|
|
|
|
|
import { renderIcon } from '@/utils/webutils.js' |
|
|
|
|
|
|
|
|
// DOM refs |
|
|
// DOM refs |
|
|
const canvasContainer = ref(null) |
|
|
const canvasContainer = ref(null) |
|
|
const guiPanel = ref(null) |
|
|
const guiPanel = ref(null) |
|
|
|
|
|
const transformEditCtl = ref(null) |
|
|
|
|
|
|
|
|
// Three.js 场景相关 |
|
|
// Three.js 场景相关 |
|
|
let scene, camera, renderer, controls |
|
|
let scene, camera, renderer, controls |
|
|
@ -88,24 +90,19 @@ let statsControls, axesHelper, gridHelper |
|
|
let gui, tcontrols, modelGroup |
|
|
let gui, tcontrols, modelGroup |
|
|
|
|
|
|
|
|
const restate = reactive({ |
|
|
const restate = reactive({ |
|
|
num, |
|
|
targetColor: '#ff0000', |
|
|
loadScale: 1 |
|
|
loadScale: 1, |
|
|
|
|
|
mode: 'translate' |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
// 状态变量 |
|
|
// 状态变量 |
|
|
const state = { |
|
|
const state = { |
|
|
showAxesHelper: true, |
|
|
showAxesHelper: true, |
|
|
showGridHelper: true, |
|
|
showGridHelper: true, |
|
|
obj: { |
|
|
|
|
|
position: { x: 0, y: 0, z: 0 }, |
|
|
|
|
|
rotation: { x: 0, y: 0, z: 0 }, |
|
|
|
|
|
scale: { x: 1, y: 1, z: 1 } |
|
|
|
|
|
}, |
|
|
|
|
|
camera: { |
|
|
camera: { |
|
|
position: { x: 0, y: 5, z: 10 }, |
|
|
position: { x: 0, y: 5, z: 10 }, |
|
|
rotation: { x: 0, y: 0, z: 0 } |
|
|
rotation: { x: 0, y: 0, z: 0 } |
|
|
}, |
|
|
} |
|
|
mode: 'translate' |
|
|
|
|
|
} |
|
|
} |
|
|
onMounted(() => { |
|
|
onMounted(() => { |
|
|
nextTick(() => { |
|
|
nextTick(() => { |
|
|
@ -114,6 +111,23 @@ onMounted(() => { |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
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 initThree() { |
|
|
function initThree() { |
|
|
const viewerDom = canvasContainer.value |
|
|
const viewerDom = canvasContainer.value |
|
|
|
|
|
|
|
|
@ -125,6 +139,8 @@ function initThree() { |
|
|
renderer = new THREE.WebGLRenderer({ |
|
|
renderer = new THREE.WebGLRenderer({ |
|
|
logarithmicDepthBuffer: true, |
|
|
logarithmicDepthBuffer: true, |
|
|
antialias: true, |
|
|
antialias: true, |
|
|
|
|
|
physicallyCorrectLights: true, |
|
|
|
|
|
outputEncoding: THREE.SRGBColorSpace, |
|
|
alpha: true, |
|
|
alpha: true, |
|
|
precision: 'mediump', |
|
|
precision: 'mediump', |
|
|
premultipliedAlpha: true, |
|
|
premultipliedAlpha: true, |
|
|
@ -133,7 +149,7 @@ function initThree() { |
|
|
}) |
|
|
}) |
|
|
renderer.clearDepth() |
|
|
renderer.clearDepth() |
|
|
renderer.shadowMap.enabled = true |
|
|
renderer.shadowMap.enabled = true |
|
|
renderer.outputColorSpace = THREE.SRGBColorSpace // 可以看到更亮的材质,同时这也影响到环境贴图。 |
|
|
renderer.toneMapping = THREE.ACESFilmicToneMapping |
|
|
renderer.setPixelRatio(window.devicePixelRatio) |
|
|
renderer.setPixelRatio(window.devicePixelRatio) |
|
|
renderer.setSize(viewerDom.getBoundingClientRect().width, viewerDom.getBoundingClientRect().height) |
|
|
renderer.setSize(viewerDom.getBoundingClientRect().width, viewerDom.getBoundingClientRect().height) |
|
|
viewerDom.appendChild(renderer.domElement) |
|
|
viewerDom.appendChild(renderer.domElement) |
|
|
@ -149,24 +165,46 @@ function initThree() { |
|
|
scene.add(gridHelper) |
|
|
scene.add(gridHelper) |
|
|
|
|
|
|
|
|
// 光照 |
|
|
// 光照 |
|
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6) |
|
|
const ambientLight = new THREE.AmbientLight(0xffffff, 1.5) |
|
|
scene.add(ambientLight) |
|
|
scene.add(ambientLight) |
|
|
// const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8) |
|
|
|
|
|
// directionalLight.position.set(10, 10, 10) |
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.5) |
|
|
// scene.add(directionalLight) |
|
|
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 = new Stats() |
|
|
|
|
|
statsControls.showPanel(0) |
|
|
statsControls.dom.style.position = 'absolute' |
|
|
statsControls.dom.style.position = 'absolute' |
|
|
|
|
|
statsControls.dom.style.bottom = '5px' |
|
|
|
|
|
statsControls.dom.style.left = '5px' |
|
|
|
|
|
statsControls.dom.style.top = 'unset' |
|
|
viewerDom.appendChild(statsControls.dom) |
|
|
viewerDom.appendChild(statsControls.dom) |
|
|
|
|
|
|
|
|
|
|
|
// // 创建几何体和材质 |
|
|
|
|
|
// const geometry = new THREE.BoxGeometry(1, 1, 1) |
|
|
|
|
|
// const material = new THREE.MeshStandardMaterial({ |
|
|
|
|
|
// color: 0xcccccc, |
|
|
|
|
|
// metalness: 0.9, |
|
|
|
|
|
// roughness: 0.1 |
|
|
|
|
|
// }) |
|
|
|
|
|
// |
|
|
|
|
|
// const cube = new THREE.Mesh(geometry, material) |
|
|
|
|
|
// scene.add(cube) |
|
|
|
|
|
// camera.position.z = 5 |
|
|
|
|
|
|
|
|
animate() |
|
|
animate() |
|
|
|
|
|
|
|
|
// 转换控制器 TransformControls |
|
|
// 转换控制器 TransformControls |
|
|
tcontrols = new TransformControls(camera, renderer.domElement) |
|
|
tcontrols = new TransformControls(camera, renderer.domElement) |
|
|
tcontrols.addEventListener('change', () => { |
|
|
tcontrols.addEventListener('change', () => { |
|
|
renderer.render(scene, camera) |
|
|
renderer.render(scene, camera) |
|
|
reloadState() |
|
|
transformEditCtl.value?.refreshData() |
|
|
}) |
|
|
}) |
|
|
tcontrols.addEventListener('dragging-changed', function(event) { |
|
|
tcontrols.addEventListener('dragging-changed', function(event) { |
|
|
controls.enabled = !event.value |
|
|
controls.enabled = !event.value |
|
|
@ -194,10 +232,6 @@ function initGUI() { |
|
|
gui = new dat.GUI({ autoPlace: false }) |
|
|
gui = new dat.GUI({ autoPlace: false }) |
|
|
guiDom.appendChild(gui.domElement) |
|
|
guiDom.appendChild(gui.domElement) |
|
|
|
|
|
|
|
|
gui.add(state, 'mode', ['translate', 'rotate', 'scale']).onChange(function(value) { |
|
|
|
|
|
tcontrols.setMode(value) |
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
// 显示辅助线 |
|
|
// 显示辅助线 |
|
|
gui.add(state, 'showAxesHelper').name('显示坐标轴').onChange(val => { |
|
|
gui.add(state, 'showAxesHelper').name('显示坐标轴').onChange(val => { |
|
|
axesHelper.visible = val |
|
|
axesHelper.visible = val |
|
|
@ -233,46 +267,6 @@ function initGUI() { |
|
|
}) |
|
|
}) |
|
|
cameraRotationFolder.close() |
|
|
cameraRotationFolder.close() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Position |
|
|
|
|
|
const positionFolder = gui.addFolder('Position') |
|
|
|
|
|
positionFolder.add(state.obj.position, 'x', -10, 10).listen().onChange(val => { |
|
|
|
|
|
modelGroup.position.x = val |
|
|
|
|
|
}) |
|
|
|
|
|
positionFolder.add(state.obj.position, 'y', -10, 10).listen().onChange(val => { |
|
|
|
|
|
modelGroup.position.y = val |
|
|
|
|
|
}) |
|
|
|
|
|
positionFolder.add(state.obj.position, 'z', -10, 10).listen().onChange(val => { |
|
|
|
|
|
modelGroup.position.z = val |
|
|
|
|
|
}) |
|
|
|
|
|
positionFolder.close() |
|
|
|
|
|
|
|
|
|
|
|
// Rotation (in radians) |
|
|
|
|
|
const rotationFolder = gui.addFolder('Rotation') |
|
|
|
|
|
rotationFolder.add(state.obj.rotation, 'x', -Math.PI, Math.PI).listen().onChange(val => { |
|
|
|
|
|
modelGroup.rotation.x = val |
|
|
|
|
|
}) |
|
|
|
|
|
rotationFolder.add(state.obj.rotation, 'y', -Math.PI, Math.PI).listen().onChange(val => { |
|
|
|
|
|
modelGroup.rotation.y = val |
|
|
|
|
|
}) |
|
|
|
|
|
rotationFolder.add(state.obj.rotation, 'z', -Math.PI, Math.PI).listen().onChange(val => { |
|
|
|
|
|
modelGroup.rotation.z = val |
|
|
|
|
|
}) |
|
|
|
|
|
rotationFolder.close() |
|
|
|
|
|
|
|
|
|
|
|
// Scale |
|
|
|
|
|
const scaleFolder = gui.addFolder('Scale') |
|
|
|
|
|
scaleFolder.add(state.obj.scale, 'x', 0.001, 10).listen().onChange(val => { |
|
|
|
|
|
modelGroup.scale.x = val |
|
|
|
|
|
}) |
|
|
|
|
|
scaleFolder.add(state.obj.scale, 'y', 0.001, 10).listen().onChange(val => { |
|
|
|
|
|
modelGroup.scale.y = val |
|
|
|
|
|
}) |
|
|
|
|
|
scaleFolder.add(state.obj.scale, 'z', 0.001, 10).listen().onChange(val => { |
|
|
|
|
|
modelGroup.scale.z = val |
|
|
|
|
|
}) |
|
|
|
|
|
scaleFolder.close() |
|
|
|
|
|
|
|
|
|
|
|
// 相机位置 |
|
|
// 相机位置 |
|
|
// const cameraFolder = gui.addFolder('相机位置') |
|
|
// const cameraFolder = gui.addFolder('相机位置') |
|
|
// cameraFolder.add(state.cameraPosition, 0, -10, 10).name('X') |
|
|
// cameraFolder.add(state.cameraPosition, 0, -10, 10).name('X') |
|
|
@ -333,7 +327,7 @@ function initMode3DCamera() { |
|
|
const viewerDom = canvasContainer.value |
|
|
const viewerDom = canvasContainer.value |
|
|
|
|
|
|
|
|
// 渲染相机 |
|
|
// 渲染相机 |
|
|
const cameraNew = new THREE.PerspectiveCamera(25, viewerDom.clientWidth / viewerDom.clientHeight, 1, 2000) |
|
|
const cameraNew = new THREE.PerspectiveCamera(25, viewerDom.clientWidth / viewerDom.clientHeight, 0.1, 2000) |
|
|
//设置相机位置 |
|
|
//设置相机位置 |
|
|
// cameraNew.position.set(4, 2, -3); |
|
|
// cameraNew.position.set(4, 2, -3); |
|
|
// cameraNew.position.set(30, 30, 30) |
|
|
// cameraNew.position.set(30, 30, 30) |
|
|
@ -371,20 +365,6 @@ function reloadState() { |
|
|
state.camera.rotation.y = camera.rotation.y |
|
|
state.camera.rotation.y = camera.rotation.y |
|
|
state.camera.rotation.z = camera.rotation.z |
|
|
state.camera.rotation.z = camera.rotation.z |
|
|
} |
|
|
} |
|
|
if (modelGroup) { |
|
|
|
|
|
state.obj.position.x = modelGroup.position.x |
|
|
|
|
|
state.obj.position.y = modelGroup.position.y |
|
|
|
|
|
state.obj.position.z = modelGroup.position.z |
|
|
|
|
|
|
|
|
|
|
|
state.obj.rotation.x = modelGroup.rotation.x |
|
|
|
|
|
state.obj.rotation.y = modelGroup.rotation.y |
|
|
|
|
|
state.obj.rotation.z = modelGroup.rotation.z |
|
|
|
|
|
|
|
|
|
|
|
state.obj.scale.x = modelGroup.scale.x |
|
|
|
|
|
state.obj.scale.y = modelGroup.scale.y |
|
|
|
|
|
state.obj.scale.z = modelGroup.scale.z |
|
|
|
|
|
console.log('state.obj', state.obj) |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function renderView() { |
|
|
function renderView() { |
|
|
@ -438,6 +418,53 @@ function handleTextureUpload(file) { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let lastObjfile = undefined |
|
|
|
|
|
|
|
|
|
|
|
function handleMtlUpload(file) { |
|
|
|
|
|
console.log('file', file) |
|
|
|
|
|
if (!file) return |
|
|
|
|
|
|
|
|
|
|
|
file = file.raw |
|
|
|
|
|
|
|
|
|
|
|
// 移除旧模型 |
|
|
|
|
|
if (modelGroup) { |
|
|
|
|
|
scene.remove(modelGroup) |
|
|
|
|
|
} |
|
|
|
|
|
tcontrols.detach() |
|
|
|
|
|
transformEditCtl.value.detach() |
|
|
|
|
|
modelGroup = null |
|
|
|
|
|
|
|
|
|
|
|
const fileName = file.name.toLowerCase() |
|
|
|
|
|
const reader = new FileReader() |
|
|
|
|
|
reader.onerror = (error) => { |
|
|
|
|
|
system.showErrorDialog('加载文件失败', error.toString()) |
|
|
|
|
|
system.clearLoading() |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if (fileName.endsWith('.mtl')) { |
|
|
|
|
|
reader.onload = () => { |
|
|
|
|
|
const mtlLoader = new MTLLoader() |
|
|
|
|
|
mtlLoader.load(reader.result, (materials) => { |
|
|
|
|
|
materials.preload() |
|
|
|
|
|
|
|
|
|
|
|
const objLoader = new OBJLoader() |
|
|
|
|
|
objLoader.setMaterials(materials) |
|
|
|
|
|
modelGroup = objLoader.parse(lastObjfile) |
|
|
|
|
|
if (restate.loadScale) { |
|
|
|
|
|
modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
scene.add(modelGroup) |
|
|
|
|
|
tcontrols.attach(modelGroup) |
|
|
|
|
|
transformEditCtl.value?.attachObject3D(modelGroup) |
|
|
|
|
|
}) |
|
|
|
|
|
} |
|
|
|
|
|
reader.readAsText(file) |
|
|
|
|
|
} else { |
|
|
|
|
|
alert('不支持的文件类型!') |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
function handleFileChange(file) { |
|
|
function handleFileChange(file) { |
|
|
console.log('file', file) |
|
|
console.log('file', file) |
|
|
if (!file) return |
|
|
if (!file) return |
|
|
@ -448,6 +475,8 @@ function handleFileChange(file) { |
|
|
if (modelGroup) { |
|
|
if (modelGroup) { |
|
|
scene.remove(modelGroup) |
|
|
scene.remove(modelGroup) |
|
|
} |
|
|
} |
|
|
|
|
|
tcontrols.detach() |
|
|
|
|
|
transformEditCtl.value.detach() |
|
|
|
|
|
|
|
|
const fileName = file.name.toLowerCase() |
|
|
const fileName = file.name.toLowerCase() |
|
|
const reader = new FileReader() |
|
|
const reader = new FileReader() |
|
|
@ -465,12 +494,10 @@ function handleFileChange(file) { |
|
|
modelGroup = content |
|
|
modelGroup = content |
|
|
if (restate.loadScale) { |
|
|
if (restate.loadScale) { |
|
|
modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale) |
|
|
modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale) |
|
|
state.obj.scale.x = modelGroup.scale.x |
|
|
|
|
|
state.obj.scale.y = modelGroup.scale.y |
|
|
|
|
|
state.obj.scale.z = modelGroup.scale.z |
|
|
|
|
|
} |
|
|
} |
|
|
scene.add(modelGroup) |
|
|
scene.add(modelGroup) |
|
|
tcontrols.attach(modelGroup) |
|
|
tcontrols.attach(modelGroup) |
|
|
|
|
|
transformEditCtl.value?.attachObject3D(modelGroup) |
|
|
|
|
|
|
|
|
controls.target.copy(modelGroup.position) |
|
|
controls.target.copy(modelGroup.position) |
|
|
controls.update() |
|
|
controls.update() |
|
|
@ -483,16 +510,15 @@ function handleFileChange(file) { |
|
|
reader.readAsText(file) |
|
|
reader.readAsText(file) |
|
|
reader.onload = () => { |
|
|
reader.onload = () => { |
|
|
const loader = new OBJLoader() |
|
|
const loader = new OBJLoader() |
|
|
|
|
|
lastObjfile = reader.result |
|
|
//@ts-ignore |
|
|
//@ts-ignore |
|
|
modelGroup = loader.parse(reader.result) |
|
|
modelGroup = loader.parse(reader.result) |
|
|
if (restate.loadScale) { |
|
|
if (restate.loadScale) { |
|
|
modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale) |
|
|
modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale) |
|
|
state.obj.scale.x = modelGroup.scale.x |
|
|
|
|
|
state.obj.scale.y = modelGroup.scale.y |
|
|
|
|
|
state.obj.scale.z = modelGroup.scale.z |
|
|
|
|
|
} |
|
|
} |
|
|
scene.add(modelGroup) |
|
|
scene.add(modelGroup) |
|
|
tcontrols.attach(modelGroup) |
|
|
tcontrols.attach(modelGroup) |
|
|
|
|
|
transformEditCtl.value?.attachObject3D(modelGroup) |
|
|
|
|
|
|
|
|
system.clearLoading() |
|
|
system.clearLoading() |
|
|
} |
|
|
} |
|
|
@ -506,12 +532,10 @@ function handleFileChange(file) { |
|
|
modelGroup = content |
|
|
modelGroup = content |
|
|
if (restate.loadScale) { |
|
|
if (restate.loadScale) { |
|
|
modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale) |
|
|
modelGroup.scale.set(restate.loadScale, restate.loadScale, restate.loadScale) |
|
|
state.obj.scale.x = modelGroup.scale.x |
|
|
|
|
|
state.obj.scale.y = modelGroup.scale.y |
|
|
|
|
|
state.obj.scale.z = modelGroup.scale.z |
|
|
|
|
|
} |
|
|
} |
|
|
scene.add(modelGroup) |
|
|
scene.add(modelGroup) |
|
|
tcontrols.attach(modelGroup) |
|
|
tcontrols.attach(modelGroup) |
|
|
|
|
|
transformEditCtl.value?.attachObject3D(modelGroup) |
|
|
|
|
|
|
|
|
system.clearLoading() |
|
|
system.clearLoading() |
|
|
} |
|
|
} |
|
|
@ -522,8 +546,6 @@ function handleFileChange(file) { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
window['state'] = state |
|
|
|
|
|
|
|
|
|
|
|
</script> |
|
|
</script> |
|
|
<style scoped lang="less"> |
|
|
<style scoped lang="less"> |
|
|
.model3d-view { |
|
|
.model3d-view { |
|
|
@ -532,6 +554,31 @@ window['state'] = state |
|
|
flex-grow: 1; |
|
|
flex-grow: 1; |
|
|
overflow: hidden; |
|
|
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 { |
|
|
.toolbar { |
|
|
padding: 10px; |
|
|
padding: 10px; |
|
|
background: #f5f5f5; |
|
|
background: #f5f5f5; |
|
|
@ -562,46 +609,16 @@ window['state'] = state |
|
|
position: relative; |
|
|
position: relative; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
.model3d-gui-wrap{ |
|
|
.model3d-gui-wrap { |
|
|
border-left: 1px solid #ccc; |
|
|
border-left: 1px solid #ccc; |
|
|
background: #111; |
|
|
background: #111; |
|
|
width: 100%; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
height: 100%; |
|
|
overflow-y: auto; |
|
|
overflow-y: auto; |
|
|
} |
|
|
} |
|
|
.gui-toolbar{ |
|
|
|
|
|
color:#fff; |
|
|
|
|
|
.gui-row{ |
|
|
|
|
|
display: flex; |
|
|
|
|
|
flex-direction: row; |
|
|
|
|
|
gap: 3px; |
|
|
|
|
|
padding:3px 0; |
|
|
|
|
|
.gui-item-name{ |
|
|
|
|
|
width: 26px; |
|
|
|
|
|
align-self: stretch; |
|
|
|
|
|
display: flex; |
|
|
|
|
|
align-items: center; |
|
|
|
|
|
justify-content: center; |
|
|
|
|
|
.el-icon{ |
|
|
|
|
|
font-size: 16px; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
.gui-item{ |
|
|
|
|
|
flex:1; |
|
|
|
|
|
text-align: center; |
|
|
|
|
|
font-size: 12px; |
|
|
|
|
|
.el-input-number{ |
|
|
|
|
|
width: 100%; |
|
|
|
|
|
.el-input__wrapper{ |
|
|
|
|
|
background-color: #424242; |
|
|
|
|
|
box-shadow:none |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
.gui-panel { |
|
|
.gui-panel { |
|
|
:deep(.lil-gui.root){ |
|
|
:deep(.lil-gui.root) { |
|
|
width: 100%; |
|
|
width: 100%; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|