欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

基于Vue3+Three.js實現(xiàn)一個3D模型可視化編輯系統(tǒng)

 更新時間:2023年09月27日 09:19:16   作者:答案answer  
這篇文章主要介紹了基于Vue3+Three.js實現(xiàn)一個3D模型可視化編輯系統(tǒng),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

前言

1.因為之前工作過的可視化大屏項目開發(fā)3d大屏組件模塊需要用到Three.js來完成,其主功能是實現(xiàn)對3d模型的材質(zhì),燈光,背景,動畫。等屬性進行可視化的編輯操作以及模型編輯數(shù)據(jù)的存儲和模型在大屏上面的拖拽顯示

2.因為是第一次使用Three.js開發(fā)實際的項目,在開發(fā)這些功能的過程中也遇到了許多Three.js的坑(好在最終都解決了)

3.同時在開發(fā)這個項目模塊的過程中也發(fā)現(xiàn)github能夠搜索到的Three.js3d模型可視化編輯相關(guān)的開源項目非常的少,許多的three.js相關(guān)問題和功能的實現(xiàn)在網(wǎng)上也很難搜索到答案

4.因此在參考了之前工作項目中做過的可視化大屏項目的3D模型編輯模塊的功能和three.js 官方編輯器 https://threejs.org/editor/的部分功能的基礎(chǔ)之上開發(fā)了一款基于Three.js+Vue3的3d模塊可視化編輯器系統(tǒng),其主要目的是盡可能更多的將three.js提供的API結(jié)合在實際的項目中去使用,作為自己個人學習three.js的記錄,也供大家學習和參考

項目的在線訪問地址:https://zhang_6666.gitee.io/three.js3d/

系統(tǒng)界面圖:

實現(xiàn)的主要功能模塊

  • 背景模塊:實現(xiàn)背景圖、全景圖、背景顏色的編輯功能
  • 材質(zhì)模塊:實現(xiàn)模型材質(zhì)顏色、透明度、網(wǎng)格、材質(zhì)顯示/隱藏、材質(zhì)貼圖、模型材質(zhì)類型切換等編輯功能
  • 后期處理模塊:實現(xiàn)模型材質(zhì)的輝光效果強度、半徑、閾值、色調(diào)曝光度、模型的拖拽和分解等編輯功能
  • 燈光模塊:實現(xiàn)環(huán)境光、點光源、半球光、聚光燈等參數(shù)的編輯功能
  • 動畫模塊:實現(xiàn)模型自帶動畫的播放、播放速度、播放類型、動作幅度和模型x,y,z軸動畫等編輯功能
  • 輔助線/軸配置模塊:實現(xiàn)模型的軸坐標、軸位置、網(wǎng)格輔助線、模型骨架、模型坐標軸輔助線等編輯功能
  • 幾何體模型配置模塊:實現(xiàn)對Three.js中的幾何體API函數(shù)的參數(shù)編輯功能
  • 模型加載模塊:實現(xiàn)模型的點擊切換功能、外部模型加載的功能、幾何體模型拖拽加載功能、支持多類型(.glb,.obj,.gltf,.fbx)格式的模型文件加載,模型加載進度條功能
  • 導出模塊:實現(xiàn)模型場景封面下載、模型文件導出功能
  • 數(shù)據(jù)保存模塊:實現(xiàn)模塊編輯數(shù)據(jù)的預覽、模型編輯數(shù)據(jù)的保存
  • 模型庫模塊:支持多個編輯模型數(shù)據(jù)的拖拽展示和保存

主要功能模塊實現(xiàn)的代碼

1.這里首先將three.js相關(guān)的API操作封裝在一個renderModel.js的class類函數(shù)中去方便在vue頁面中調(diào)用

2.將不同模塊的功能都寫入函數(shù)方法中去,將需要編輯操作的一些three.js的API屬性定義在constructor中去然后在通過this去修改

import * as THREE from 'three' //導入整個 three.js核心庫
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' //導入控制器模塊,軌道控制器
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader' //導入GLTF模塊,模型解析器,根據(jù)文件格式來定
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
import { OutlinePass } from 'three/examples/jsm/postprocessing/OutlinePass.js'
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js'
import { FXAAShader } from 'three/examples/jsm/shaders/FXAAShader.js'
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'
import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter'
import { DragControls } from 'three/examples/jsm/controls/DragControls';
import { ElMessage } from 'element-plus';
import { lightPosition, onlyKey } from '@/utils/utilityFunction'
import store from '@/store'
import TWEEN from "@tweenjs/tween.js";
import { vertexShader, fragmentShader, MODEL_DECOMPOSE } from '@/config/constant.js'
// 定義一個 class類
class renderModel {
    constructor(selector) {
		this.container = document.querySelector(selector)
		// 相機
		this.camera
		// 場景
		this.scene
		//渲染器
		this.renderer
		// 控制器
		this.controls
		// 模型
		this.model
		// 幾何體模型數(shù)組
		this.geometryGroup = new THREE.Group()
		// 幾何體模型
		this.geometryModel
		// 加載進度監(jiān)聽
		this.loadingManager = new THREE.LoadingManager()
		//文件加載器類型
		this.fileLoaderMap = {
			'glb': new GLTFLoader(),
			'fbx': new FBXLoader(this.loadingManager),
			'gltf': new GLTFLoader(),
			'obj': new OBJLoader(this.loadingManager),
		}
		//模型動畫列表
		this.modelAnimation
		//模型動畫對象
		this.animationMixer
		this.animationColock = new THREE.Clock()
		//動畫幀
		this.animationFrame = null
		// 軸動畫幀
		this.rotationAnimationFrame = null
		// 動畫構(gòu)造器
		this.animateClipAction = null
		// 動畫循環(huán)方式枚舉
		this.loopMap = {
			LoopOnce: THREE.LoopOnce,
			LoopRepeat: THREE.LoopRepeat,
			LoopPingPong: THREE.LoopPingPong
		}
		//模型材質(zhì)列表
		this.modelMaterialList
		// 效果合成器
		this.effectComposer
		this.outlinePass
		// 動畫渲染器
		this.renderAnimation = null
		// 碰撞檢測
		this.raycaster = new THREE.Raycaster()
		// 鼠標位置
		this.mouse = new THREE.Vector2()
		// 模型自帶貼圖
		this.modelTextureMap
		// 輝光效果合成器
		this.glowComposer
		// 輝光渲染器
		this.unrealBloomPass
		// 需要輝光的材質(zhì)
		this.glowMaterialList
		this.materials = {}
		// 拖拽對象控制器
		this.dragControls
		// 是否開啟輝光
		this.glowUnrealBloomPass = false
		// 窗口變化監(jiān)聽事件
		this.onWindowResizesListener
		// 模型上傳進度條回調(diào)函數(shù)
		this.modelProgressCallback = (e) => e
	}
        	init() {
		return new Promise(async (reslove, reject) => {
			//初始化渲染器
			this.initRender()
			//初始化相機
			this.initCamera()
			//初始化場景
			this.initScene()
			//初始化控制器,控制攝像頭,控制器一定要在渲染器后
			this.initControls()
			this.addEvenListMouseLisatener()
			// 添加物體模型 TODO:初始化時需要默認一個
			const load = await this.setModel({ filePath: 'threeFile/glb/glb-9.glb', fileType: 'glb', decomposeName: 'transformers_3' })
			// 創(chuàng)建效果合成器
			this.createEffectComposer()
			//場景渲染
			this.sceneAnimation()
			reslove(load)
		})
	}
	// 創(chuàng)建場景
	initScene() {
		this.scene = new THREE.Scene()
		const texture = new THREE.TextureLoader().load(require('@/assets/image/view-4.png'))
		texture.mapping = THREE.EquirectangularReflectionMapping
		this.scene.background = texture
		this.scene.environment = texture
	}
	// 創(chuàng)建相機
	initCamera() {
		const { clientHeight, clientWidth } = this.container
		this.camera = new THREE.PerspectiveCamera(50, clientWidth / clientHeight, 0.25, 2000)
	}
	// 創(chuàng)建渲染器
	initRender() {
		this.renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true, preserveDrawingBuffer: true }) //設(shè)置抗鋸齒
		//設(shè)置屏幕像素比
		this.renderer.setPixelRatio(window.devicePixelRatio)
		//渲染的尺寸大小
		const { clientHeight, clientWidth } = this.container
		this.renderer.setSize(clientWidth, clientHeight)
		//色調(diào)映射
		this.renderer.toneMapping = THREE.ReinhardToneMapping
		this.renderer.autoClear = true
		this.renderer.outputColorSpace = THREE.SRGBColorSpace
		//曝光
		this.renderer.toneMappingExposure = 3
		this.renderer.shadowMap.enabled = true
		this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
		this.container.appendChild(this.renderer.domElement)
	}
	// 更新場景
	sceneAnimation() {
		this.renderAnimation = requestAnimationFrame(() => this.sceneAnimation())
		// 將不需要處理輝光的材質(zhì)進行存儲備份
		this.scene.traverse((v) => {
			if (v instanceof THREE.Scene) {
				this.materials.scene = v.background
				v.background = null
			}
			if (!this.glowMaterialList.includes(v.name) && v.isMesh) {
				this.materials[v.uuid] = v.material
				v.material = new THREE.MeshStandardMaterial({ color: 'black' })
			}
		})
		this.glowComposer.render()
		// 在輝光渲染器執(zhí)行完之后在恢復材質(zhì)原效果
		this.scene.traverse((v) => {
			if (this.materials[v.uuid]) {
				v.material = this.materials[v.uuid]
				delete this.materials[v.uuid]
			}
			if (v instanceof THREE.Scene) {
				v.background = this.materials.scene
				delete this.materials.scene
			}
		})
		this.controls.update()
		TWEEN.update();
		this.effectComposer.render()
	}
	// 監(jiān)聽事件
	addEvenListMouseLisatener() {
		//監(jiān)聽場景大小改變,跳轉(zhuǎn)渲染尺寸
		this.onWindowResizesListener = this.onWindowResizes.bind(this)
		window.addEventListener("resize", this.onWindowResizesListener)
		// 鼠標點擊
		this.onMouseClickListener = this.onMouseClickModel.bind(this)
		this.container.addEventListener('click', this.onMouseClickListener)
	}
	// 創(chuàng)建控制器
	initControls() {
		this.controls = new OrbitControls(this.camera, this.renderer.domElement)
		this.controls.enablePan = false
	}
	// 加載模型
	setModel({ filePath, fileType, scale, map, position, decomposeName }) {
		return new Promise((resolve, reject) => {
			const loader = this.fileLoaderMap[fileType]
			if (['glb', 'gltf'].includes(fileType)) {
				const dracoLoader = new DRACOLoader()
				dracoLoader.setDecoderPath('./threeFile/gltf/')
				loader.setDRACOLoader(dracoLoader)
			}
			loader.load(filePath, (result) => {
				switch (fileType) {
					case 'glb':
						this.model = result.scene
						this.skeletonHelper = new THREE.SkeletonHelper(result.scene)
						this.modelAnimation = result.animations || []
						break;
					case 'fbx':
						this.model = result
						this.skeletonHelper = new THREE.SkeletonHelper(result)
						this.modelAnimation = result.animations || []
						break;
					case 'gltf':
						this.model = result.scene
						this.skeletonHelper = new THREE.SkeletonHelper(result.scene)
						this.modelAnimation = result.animations || []
						break;
					case 'obj':
						this.model = result
						this.skeletonHelper = new THREE.SkeletonHelper(result)
						this.modelAnimation = result.animations || []
						break;
					default:
						break;
				}
				this.model.decomposeName = decomposeName
				this.getModelMeaterialList(map)
				this.setModelPositionSize()
				//	設(shè)置模型大小
				if (scale) {
					this.model.scale.set(scale, scale, scale);
				}
				//設(shè)置模型位置 
				this.model.position.set(0, -.5, 0)
				if (position) {
					const { x, y, z } = position
					this.model.position.set(x, y, z)
				}
				this.skeletonHelper.visible = false
				this.scene.add(this.skeletonHelper)
				// 需要輝光的材質(zhì)
				this.glowMaterialList = this.modelMaterialList.map(v => v.name)
				this.scene.add(this.model)
				resolve(true)
			}, (xhr) => {
				this.modelProgressCallback(xhr.loaded)
			}, (err) => {
				ElMessage.error('文件錯誤')
				console.log(err)
				reject()
			})
		})
	}
	// 加載幾何體模型
	setGeometryModel(model) {
		return new Promise((reslove, reject) => {
			const { clientHeight, clientWidth, offsetLeft, offsetTop } = this.container
			// 計算鼠標在屏幕上的坐標
			this.mouse.x = ((model.clientX - offsetLeft) / clientWidth) * 2 - 1
			this.mouse.y = -((model.clientY - offsetTop) / clientHeight) * 2 + 1
			this.raycaster.setFromCamera(this.mouse, this.camera);
			const intersects = this.raycaster.intersectObjects(this.scene.children);
			if (intersects.length > 0) {
				// 在控制臺輸出鼠標在場景中的位置
				const { type } = model
				// 不需要賦值的key
				const notGeometrykey = ['id', 'name', 'modelType', 'type']
				const geometryData = Object.keys(model).filter(key => !notGeometrykey.includes(key)).map(v => model[v])
				// 創(chuàng)建幾何體
				const geometry = new THREE[type](...geometryData)
				const colors = ['#FF4500', '#90EE90', '#00CED1', '#1E90FF', '#C71585', '#FF4500', '#FAD400', '#1F93FF', '#90F090', '#C71585']
				// 隨機顏色
				const meshColor = colors[Math.ceil(Math.random() * 10)]
				const material = new THREE.MeshStandardMaterial({ color: new THREE.Color(meshColor),side: THREE.DoubleSide })
				const mesh = new THREE.Mesh(geometry, material)
				const { x, y, z } = intersects[0].point
				mesh.position.set(x, y, z)
				mesh.name = type + '_' + onlyKey(4, 5)
				mesh.userData.geometry = true
				this.geometryGroup.add(mesh)
				this.model = this.geometryGroup
				this.onSetGeometryMeshList(mesh)
				this.skeletonHelper.visible = false
				this.skeletonHelper.dispose()
				this.glowMaterialList = this.modelMaterialList.map(v => v.name)
				this.setModelMeshDrag({ modelDrag: true })
				this.scene.add(this.model)
			}
			reslove(true)
		})
	}
	// 模型加載進度條回調(diào)函數(shù)
	onProgress(callback) {
		if (typeof callback == 'function') {
			this.modelProgressCallback = callback
		}
	}
	// 創(chuàng)建效果合成器
	createEffectComposer() {
		const { clientHeight, clientWidth } = this.container
		this.effectComposer = new EffectComposer(this.renderer)
		const renderPass = new RenderPass(this.scene, this.camera)
		this.effectComposer.addPass(renderPass)
		this.outlinePass = new OutlinePass(new THREE.Vector2(clientWidth, clientHeight), this.scene, this.camera)
		this.outlinePass.visibleEdgeColor = new THREE.Color('#FF8C00') // 可見邊緣的顏色
		this.outlinePass.hiddenEdgeColor = new THREE.Color('#8a90f3') // 不可見邊緣的顏色
		this.outlinePass.edgeGlow = 2.0 // 發(fā)光強度
		this.outlinePass.edgeThickness = 1 // 邊緣濃度
		this.outlinePass.edgeStrength = 4 // 邊緣的強度,值越高邊框范圍越大
		this.outlinePass.pulsePeriod = 100 // 閃爍頻率,值越大頻率越低
		this.effectComposer.addPass(this.outlinePass)
		let effectFXAA = new ShaderPass(FXAAShader)
		const pixelRatio = this.renderer.getPixelRatio()
		effectFXAA.uniforms.resolution.value.set(1 / (clientWidth * pixelRatio), 1 / (clientHeight * pixelRatio))
		effectFXAA.renderToScreen = true
		effectFXAA.needsSwap = true
		this.effectComposer.addPass(effectFXAA)
		//創(chuàng)建輝光效果
		this.unrealBloomPass = new UnrealBloomPass(new THREE.Vector2(clientWidth, clientHeight),1.5, 0.4, 0.85)
		// 輝光合成器
		const renderTargetParameters = {
			minFilter: THREE.LinearFilter,
			magFilter: THREE.LinearFilter,
			format: THREE.RGBAFormat,
			stencilBuffer: false,
		};
		const glowRender = new THREE.WebGLRenderTarget(clientWidth * 2, clientHeight * 2, renderTargetParameters)
		this.glowComposer = new EffectComposer(this.renderer,glowRender)
		this.glowComposer.renderToScreen = false
		this.glowComposer.addPass(new RenderPass(this.scene, this.camera))
		this.glowComposer.addPass(this.unrealBloomPass)
		// 著色器
		let shaderPass = new ShaderPass(new THREE.ShaderMaterial({
			uniforms: {
				baseTexture: { value: null },
				bloomTexture: { value: this.glowComposer.renderTarget2.texture },
				tDiffuse: {
					value: null
				}
			},
			vertexShader,
			fragmentShader,
			defines: {}
		}), 'baseTexture')
		shaderPass.renderToScreen = true
		shaderPass.needsSwap = true
		this.effectComposer.addPass(shaderPass)
	}
	// 切換模型
	onSwitchModel(model) {
		return new Promise(async (reslove, reject) => {
			try {
				this.clearSceneModel()
				// 加載幾何模型
				if (model.modelType && model.modelType == 'geometry') {
					// 重置"燈光"模塊數(shù)據(jù)
					this.onResettingLight({ ambientLight: false })
					this.modelAnimation = []
					this.camera.fov = 80
					this.camera.updateProjectionMatrix()
					const load = await this.setGeometryModel(model)
					reslove()
				} else {
					// 重置"燈光"模塊數(shù)據(jù)
					this.onResettingLight({ ambientLight: true })
					this.camera.fov = 50
					this.geometryGroup.clear()
					// 加載模型
					const load = await this.setModel(model)
					// 模型加載成功返回 true
					reslove({ load, filePath: model.filePath })
				}
			} catch {
				reject()
			}
		})
	}
	// 監(jiān)聽窗口變化
	onWindowResizes() {
		if (!this.container) return false
		const { clientHeight, clientWidth } = this.container
		//調(diào)整屏幕大小
		this.camera.aspect = clientWidth / clientHeight //攝像機寬高比例
		this.camera.updateProjectionMatrix() //相機更新矩陣,將3d內(nèi)容投射到2d面上轉(zhuǎn)換
		this.renderer.setSize(clientWidth, clientHeight)
		this.effectComposer.setSize(clientWidth * 2, clientHeight * 2)
		this.glowComposer.setSize(clientWidth, clientHeight)
	}
}

2.在vue頁面中去使用

<template>
     <div id="model" ref="model"></div>
</template>
<script setup>
import { onMounted} from "vue";
import renderModel from "./renderModel";
const store = useStore();
const state = reactive({
  modelApi: computed(() => {
    return store.state.modelApi;
  })
 });
const loading = ref(false);
const progress = ref(0);
// 初始化場景方法
onMounted(async () => {
  loading.value = true;
  const modelApi = new renderModel("#model");
  //將當前場景函數(shù)存儲在vuex中
  store.commit("SET_MODEL_API", modelApi);
  // 模型加載進度條
  state.modelApi.onProgress((progressNum) => {
    progress.value = Number((progressNum / 1024 / 1024).toFixed(2));
    // console.log('模型已加載' + progress.value + 'M')
  });
  const load = await modelApi.init();
  // load=true 表示模型加載完成(主要針對大模型文件)
  if (load) {
    loading.value = false;
    progress.value = 0;
  }
});

3.ok這樣一個模型編輯器的初始化場景功能就完成了

如何將編輯的模型數(shù)據(jù)進行存儲和回顯???

  • 模型數(shù)據(jù)的存儲和回顯應(yīng)該是這個編輯器最核心的東西了吧,我想你也不希望自己編輯操作了半天的模型數(shù)據(jù)被瀏覽器的F5一鍵重置了吧。
  • 這里我的思路是將模型的背景、燈光、材質(zhì)、動畫、輔助線、位置等屬性值存儲在localStorage ,在頁面刷新或者進入頁面時候獲取到這些保存的數(shù)據(jù)值,然后將這些值進行數(shù)據(jù)回填。這種思路同樣也適用于將數(shù)據(jù)存儲在服務(wù)端然后在通過調(diào)用接口獲取。
  • 新建一個initThreeTemplate.js 文件 用于專門處理模型數(shù)據(jù)回填 (renderModel) 方法 和創(chuàng)建模型渲染 (createThreeDComponent) 方法。
  • renderModel 方法內(nèi)容和上面的基本一致,只是在傳遞和接收參數(shù)時新增一個模型數(shù)據(jù)的參數(shù) config,這里只列舉部分不同處的代碼作為解釋
/**
 * @describe three.js 組件數(shù)據(jù)初始化方法
 * @param config 組件參數(shù)配置信息
 * @param elementId 元素ID
*/
class renderModel {
    constructor(config, elementId) {
         this.config = config  
     }
  // 獲取到創(chuàng)建相機位置
	initCamera() {
		const { clientHeight, clientWidth } = this.container
		this.camera = new THREE.PerspectiveCamera(45, clientWidth / clientHeight, 0.25, 1000)
		this.camera.near = 0.1
		const { camera } = this.config
		if (!camera) return false
		const { x, y, z } = camera
		this.camera.position.set(x, y, z)
		this.camera.updateProjectionMatrix()
	}
    // 設(shè)置輝光和模型操作數(shù)據(jù)回填
	setModelLaterStage() {
		const { stage } = this.config
		if (!stage) return false
		const { threshold, strength, radius, toneMappingExposure, meshPositonList } = stage
		// 設(shè)置輝光效果
		if (stage.glow) {
			this.unrealBloomPass.threshold = threshold
			this.unrealBloomPass.strength = strength
			this.unrealBloomPass.radius = radius
			this.renderer.toneMappingExposure = toneMappingExposure
		} else {
			this.unrealBloomPass.threshold = 0
			this.unrealBloomPass.strength = 0
			this.unrealBloomPass.radius = 0
			this.renderer.toneMappingExposure = toneMappingExposure
		}
		// 模型材質(zhì)位置
		meshPositonList.forEach(v => {
			const mesh = this.model.getObjectByProperty('name', v.name)
			const { x, y, z } = v
			mesh.position.set(x, y, z)
		})
	}
	// 處理模型動畫數(shù)據(jù)回填
	setModelAnimation() {
		const { animation } = this.config
		if (!animation) return false
		if (this.modelAnimation.length && animation && animation.visible) {
			this.animationMixer = new THREE.AnimationMixer(this.model)
			const { animationName, timeScale, weight, loop } = animation
			// 模型動畫
			const clip = THREE.AnimationClip.findByName(this.modelAnimation, animationName)
			if (clip) {
				this.animateClipAction = this.animationMixer.clipAction(clip)
				this.animateClipAction.setEffectiveTimeScale(timeScale)
				this.animateClipAction.setEffectiveWeight(weight)
				this.animateClipAction.setLoop(this.loopMap[loop])
				this.animateClipAction.play()
			}
			this.animationFrameFun()
		}
		// 軸動畫
		if (animation.rotationVisible) {
			const { rotationType, rotationSpeed } = animation
			this.rotationAnimationFun(rotationType, rotationSpeed)
		}
	}
	// 模型動畫幀
	animationFrameFun() {
		this.animationFrame = requestAnimationFrame(() => this.animationFrameFun())
		if (this.animationMixer) {
			this.animationMixer.update(this.animationColock.getDelta())
		}
	}
	// 軸動畫幀
	rotationAnimationFun(rotationType, rotationSpeed) {
		this.rotationAnimationFrame = requestAnimationFrame(() => this.rotationAnimationFun(rotationType, rotationSpeed))
		this.model.rotation[rotationType] += rotationSpeed / 50
	}
	// 模型軸輔助線配置
	setModelAxleLine() {
		const { attribute } = this.config
		if (!attribute) return false
		const { axesHelper, axesSize, color, divisions, gridHelper, positionX, positionY, positionZ, size, skeletonHelper, visible, x, y, z, rotationX, rotationY, rotationZ } = attribute
		if (!visible) return false
		//網(wǎng)格輔助線
		this.gridHelper = new THREE.GridHelper(size, divisions, color, color);
		this.gridHelper.position.set(x, y, z)
		this.gridHelper.visible = gridHelper
		this.gridHelper.material.linewidth = 0.1
		this.scene.add(this.gridHelper)
		// 坐標軸輔助線
		this.axesHelper = new THREE.AxesHelper(axesSize);
		this.axesHelper.visible = axesHelper
		this.axesHelper.position.set(0, -.50, 0)
		this.scene.add(this.axesHelper);
		// 設(shè)置模型位置
		this.model.position.set(positionX, positionY, positionZ)
		// 設(shè)置模型軸位置
		this.model.rotation.set(rotationX, rotationY, rotationZ)
		// 開啟陰影
		this.renderer.shadowMap.enabled = true;
		// 骨骼輔助線
		this.skeletonHelper = new THREE.SkeletonHelper(this.model)
		this.skeletonHelper = skeletonHelper
	}
}  

6 在頁面中調(diào)用方法,獲取到 localStorage 然后傳入 createThreeDComponent 方法中去這樣一個模型渲染和數(shù)據(jù)回填的功能就實現(xiàn)了。沒錯就是這么簡單

<template>
  <div id="preview">
    <tree-component />
  </div>
</template>
<script setup lang="jsx" name="modelBase">
import { local } from "@/utils/storage";
import createThreeDComponent from "@/utils/initThreeTemplate";
import { MODEL_PRIVEW_CONFIG } from "@/config/constant";
// 獲取 localStorage 的模型編輯數(shù)據(jù)
const config = local.get(MODEL_PRIVEW_CONFIG);
const treeComponent = createThreeDComponent(config);
</script>
<style lang="less" scoped>
#preview {
  width: 100%;
  height: 100vh;
}
</style>

模型編輯的數(shù)據(jù) MODEL_PRIVEW_CONFI 的結(jié)構(gòu)

數(shù)據(jù)回顯效果

如何實現(xiàn)多模型的數(shù)據(jù)回顯展示

1 這里通過列表循環(huán)渲染和 vue3-draggable-resizable 插件實現(xiàn) 可拖拽的多模型展示功能

<template>
      <div id="drag-content">
        <div class="content" @drop="onDrop" @dragover.prevent>
          <draggable-container :adsorbParent="true" :disabled="true">
            <draggable-resizable-item
              @onDragActived="onDragActived"
              @onDragDeactivated="onDragDeactivated"
              v-for="drag in dragModelList"
              :key="drag.modelKey"
              :config="drag"
            ></draggable-resizable-item>
          </draggable-container>
        </right-context-menu>
        </div>
      </div>
 </template>
 <script setup name="modelBase">
 import DraggableResizableItem from "@/components/DraggableResizableItem/index";
 const dragModelList = ref([]);
 // 當前選中的內(nèi)容
 const dragActive = ref(null);
 const onDrop = (event) => {
  event.preventDefault();
  // 設(shè)置模型拖放位置
  const container = document.querySelector("#drag-content").getBoundingClientRect();
  const x = event.clientX - container.left - 520 / 2;
  const y = event.clientY - container.top - 360 / 2;
  dragActive.value.x = x;
  dragActive.value.y = y;
};
 // 選中拖拽元素
const onDragActived = (drag) => {
  dragActive.value = drag;
};
// 取消選中拖拽元素
const onDragDeactivated = (modelKey) => {
  if (modelKey == dragActive.value.modelKey) {
    dragActive.value = null;
  }
};
// 
 </script>

DraggableResizableItem.vue 代碼

<template>
  <draggable-resizable
    class="draggable-resizable"
    classNameDragging="dragging"
    classNameActive="active"
    :initW="props.config.width"
    :initH="props.config.height"
    v-model:x="props.config.x"
    v-model:y="props.config.y"
    v-model:w="props.config.width"
    v-model:h="props.config.height"
    :parent="false"
    :resizable="true"
    :draggable="true"
    @drag-end="dragEndHandle"
    @dragging="dragHandle"
    @activated="activatedHandle"
    @deactivated="deactivatedHandle"
  >
    <tree-component
      :width="props.config.width"
      :height="props.config.height"
    ></tree-component>
    <div :class="dragMask" class="mask"></div>
  </draggable-resizable>
</template>
<script setup>
import DraggableResizable from "vue3-draggable-resizable";
import createThreeDComponent from "@/utils/initThreeTemplate";
import { ref   } from "vue";
const props = defineProps({
  config: {
    type: Object,
    default: {},
  },
});
const emit = defineEmits(["onDragActived", "onDragDeactivated"]);
const dragMask = ref("");
// 開始拖拽
const dragHandle = (e) => {
  dragMask.value = "mask-dragging";
};
// 拖拽結(jié)束
const dragEndHandle = (e) => {
  dragMask.value = "mask-dragactive";
};
// 選中
const activatedHandle = (e) => {
  dragMask.value = "mask-dragactive";
  emit("onDragActived", props.config);
};
// 取消選中
const deactivatedHandle = (e) => {
  dragMask.value = "";
  emit("onDragDeactivated", props.config.modelKey);
};
const treeComponent = createThreeDComponent(props.config);
</script>

數(shù)據(jù)回顯效果

結(jié)語

1.好了這樣一個基于Three.js開發(fā)的3d模型可視化編輯系統(tǒng)就開發(fā)完成了

2.完整的項目代碼可訪問:

gitee:https://gitee.com/ZHANG_6666/Three.js3D

github:https://github.com/zhangbo126/Three3d-view

以上就是基于Vue3+Three.js實現(xiàn)一個3D模型可視化編輯系統(tǒng)的詳細內(nèi)容,更多關(guān)于Vue3+Three.js實現(xiàn)3D模型可視化系統(tǒng)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue3 reactive定義的引用類型直接賦值導致數(shù)據(jù)失去響應(yīng)式問題

    vue3 reactive定義的引用類型直接賦值導致數(shù)據(jù)失去響應(yīng)式問題

    這篇文章主要介紹了vue3 reactive定義的引用類型直接賦值導致數(shù)據(jù)失去響應(yīng)式問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • Vue獲取DOM的幾種方法總結(jié)

    Vue獲取DOM的幾種方法總結(jié)

    這篇文章主要介紹了Vue獲取DOM的幾種方法總結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • 詳解Vue3-pinia狀態(tài)管理

    詳解Vue3-pinia狀態(tài)管理

    這篇文章主要介紹了Vue3-pinia狀態(tài)管理,pinia是?vue3?新的狀態(tài)管理工具,簡單來說相當于之前?vuex,它去掉了?Mutations?但是也是支持?vue2?的,需要的朋友可以參考下
    2022-08-08
  • vue中動態(tài)select的使用方法示例

    vue中動態(tài)select的使用方法示例

    這篇文章主要介紹了vue中動態(tài)select的使用方法,結(jié)合實例形式分析了vue.js使用動態(tài)select創(chuàng)建下拉菜單相關(guān)實現(xiàn)技巧與操作注意事項,需要的朋友可以參考下
    2019-10-10
  • 示例詳解Vue中控制組件的掛載位置

    示例詳解Vue中控制組件的掛載位置

    在 Vue 中,append-to-body=“true” 主要用于一些第三方組件(如 Element UI 或 Ant Design Vue 中的彈出框、下拉菜單等)來控制組件的掛載位置,本文介紹Vue中控制組件的掛載位置,感興趣的朋友跟隨小編一起看看吧
    2024-12-12
  • vue-router 路由傳參用法實例分析

    vue-router 路由傳參用法實例分析

    這篇文章主要介紹了vue-router 路由傳參用法,結(jié)合實例形式分析了vue-router 路由傳參基本使用方法及操作注意事項,需要的朋友可以參考下
    2020-03-03
  • Vue實現(xiàn)登錄保存token并校驗實現(xiàn)保存登錄狀態(tài)的操作代碼

    Vue實現(xiàn)登錄保存token并校驗實現(xiàn)保存登錄狀態(tài)的操作代碼

    這篇文章主要介紹了Vue實現(xiàn)登錄保存token并校驗實現(xiàn)保存登錄狀態(tài),本文通過示例代碼給大家介紹的非常詳細,感興趣的朋友跟隨小編一起看看吧
    2024-02-02
  • Vue常見報錯整理大全(從此報錯不害怕)

    Vue常見報錯整理大全(從此報錯不害怕)

    寫代碼的過程中一定會遇到報錯,遇到報錯不要擔心,認真分析就可以解決報錯,同時積累經(jīng)驗,早日成為大牛,這篇文章主要給大家介紹了關(guān)于Vue常見報錯整理的相關(guān)資料,需要的朋友可以參考下
    2022-08-08
  • 前端Vue如何獲取登錄的用戶名或用戶id代碼實例

    前端Vue如何獲取登錄的用戶名或用戶id代碼實例

    在前端開發(fā)中,獲取登錄用戶的用戶名是一項常見的需求,這篇文章主要給大家介紹了關(guān)于前端Vue如何獲取登錄的用戶名或用戶id的相關(guān)資料,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-07-07
  • Vue中mixins混入的介紹和使用詳解

    Vue中mixins混入的介紹和使用詳解

    mixins(混入)是一種分發(fā)?Vue?組件中可復用功能的非常靈活的方式,這篇文章主要為大家介紹了mixins混入的介紹和使用,需要的可以參考下
    2023-08-08

最新評論