threejs-3dmodel-edit多模型编辑功能threejs3d模型编辑器实现多模型编辑功能,通过拖拽在3d场景
前言
在陆续收获很多私信和git issues 中也提到能否实现多模型编辑的场景后,也是决定尝试在 threejs-3dmodel-edit项目中实现多模型编辑的场景功能
最终实现效果
话不多说以下是具体实现的代码
1.通过拖拽的方式在场景中添加多个模型
新增拖拽方法 onDragModelStart(拖拽开始) onDragModelStart(拖拽结束)
<script setup name="modelEdit">
import { onMounted, ref, getCurrentInstance, onBeforeUnmount, computed } from "vue";
import { useMeshEditStore } from "@/store/meshEditStore";
const store = useMeshEditStore();
const { $bus, $local } = getCurrentInstance().proxy;
// 拖拽模型开始
const onDragModelStart = model => {
store.changeDragType("manyModel");
store.modelApi.setDragManyModel(model);
};
// 当前拖拽结束TODO:geometry(几何体模型) tags(3d标签) manyModel(多模型)
const onDragDrop = async e => {
const { dragGeometryModel, dragTag, activeDragManyModel } = store.modelApi;
const { clientX, clientY } = e;
// 几何体
if (dragGeometryModel.id && store.modelType == "geometry") {
dragGeometryModel.clientX = clientX;
dragGeometryModel.clientY = clientY;
store.modelApi.onSwitchModel(dragGeometryModel);
// 更新当前编辑tab
$bus.emit("update-tab", "EditGeometry");
}
// 3d标签
if (dragTag.id && store.modelType == "tags") {
dragTag.clientX = clientX;
dragTag.clientY = clientY;
store.modelApi.create3dTags(dragTag);
}
// 多模型
if (store.modelType == "manyModel") {
activeDragManyModel.clientX = clientX;
activeDragManyModel.clientY = clientY;
$bus.emit("page-loading", true);
try {
const { load } = await store.modelApi.onLoadManyModel(activeDragManyModel);
if (load) {
$bus.emit("update-model");
// 更新当前编辑tab
$bus.emit("update-tab", "EditMoreModel");
$bus.emit("page-loading", false);
}
} catch {
$bus.emit("page-loading", false);
}
}
};
</script>
2.定义多模型加载方法
在renderModel.js 中新增一个 onLoadManyModel 方法用于加载多模型,
在原加载普通模型代码的基础上新增 获取当前鼠标在3d场景中所处位置的方法
onLoadManyModel(model) {
return new Promise((resolve, reject) => {
const { clientHeight, clientWidth, offsetLeft, offsetTop } = this.container
const { filePath, fileType, name } = model
// 计算鼠标在屏幕上的坐标
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, true);
if (intersects.length > 0) {
this.loadingStatus = false
let loader
if (['glb', 'gltf'].includes(fileType)) {
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath(`draco/gltf/`)
dracoLoader.setDecoderConfig({ type: 'js' })
dracoLoader.preload()
loader = new GLTFLoader().setDRACOLoader(dracoLoader)
} else {
loader = this.fileLoaderMap[fileType]
}
let manyModel
loader.load(filePath, (result) => {
switch (fileType) {
case 'glb':
manyModel = result.scene
break;
case 'fbx':
manyModel = result
break;
case 'gltf':
manyModel = result.scene
break;
case 'obj':
manyModel = result
break;
case 'stl':
const material = new THREE.MeshStandardMaterial();
const mesh = new THREE.Mesh(result, material);
manyModel = mesh
break;
default:
break;
}
// console.log(result, '================')
this.getManyModelAnimationList(result.animations)
// 设置模型位置
const { x, y, z } = intersects[0].point
manyModel.position.set(x, y, z)
const box = new THREE.Box3().setFromObject(manyModel);
const size = box.getSize(new THREE.Vector3());
const maxSize = Math.max(size.x, size.y, size.z);
const targetSize = 1.2;
const scale = targetSize / (maxSize > 1 ? maxSize : .5);
manyModel.scale.set(scale, scale, scale)
manyModel.name = name
manyModel.userData = {
type: 'manyModel',
...manyModel.userData
}
this.manyModelGroup.add(manyModel)
this.model = this.manyModelGroup
this.outlinePass.renderScene = this.model
this.getModelMaterialList()
// 需要辉光的材质
this.glowMaterialList = this.modelMaterialList.map(v => v.name)
this.scene.add(this.model)
this.loadingStatus = true
resolve({ load: true })
}, (xhr) => {
this.modelProgressCallback(xhr.loaded)
}, (err) => {
ElMessage.error(err)
reject()
})
}
else {
reject()
ElMessage.warning('当前角度无法获取鼠标位置请调整“相机角度”在添加')
}
})
}
3.模型选中和模型删除功能
通过以上两步已经可以实现在场景中拖拽添加模型了,既然有添加模型,那模型删除功能肯定是必不可少的 同时为了准确的知道当前选择的模型,也是专门做了一个模型选中的功能
模型选中效果:
import * as THREE from 'three'
// 选择当前模型
function chooseManyModel(uuid) {
// 通过模型uuid获取到模型
const manyModel = this.scene.getObjectByProperty('uuid', uuid)
// 给模型添加选中效果
this.outlinePass.visibleEdgeColor = new THREE.Color('#409eff') // 可见边缘的颜色
this.outlinePass.hiddenEdgeColor = new THREE.Color('#0099cc') // 不可见边缘的颜色
this.outlinePass.selectedObjects = [manyModel]
// console.log(manyModel, '===============')
if (manyModel) {
const { position, rotation, userData, scale } = manyModel.clone()
manyModel.userData = {
...userData,
position,
rotation,
scale
}
return {
position: { ...position },
rotation: { ...rotation },
scale: scale.x
}
}
return {}
}
// 模型删除功能
function deleteManyModel(uuid) {
const manyModel = this.scene.getObjectByProperty('uuid', uuid)
if (!manyModel) return
this.manyModelGroup.remove(manyModel)
this.outlinePass.selectedObjects = []
}
4.模型位置,模型轴,模型缩放功能
在有了添加模型和删除模型功能后,可以满足多模型编辑的基本诉求了。为了更加方便的操作模型也是新增了三个小功能:编辑模型位置(x,y,z)轴 , 模型轴方向(x,y,z)轴,模型大小缩放以及重置功能
通过 tween.js 实现过渡效果
import * as THREE from 'three'
import TWEEN from "@tweenjs/tween.js";
// 设置模型轴方向
function setManyModelRotation(type, flag, uuid) {
const manyModel = this.scene.getObjectByProperty('uuid', uuid)
if (!manyModel) return
const maxAxis = Math.PI / 2
const { x, y, z } = manyModel.rotation
const endPosition = {
x, y, z
}
endPosition[type] += flag ? maxAxis : -maxAxis
const Tween = new TWEEN.Tween({ x, y, z })
Tween.to(endPosition, 500)
Tween.onUpdate((val) => {
manyModel.rotation[type] = val[type]
})
Tween.start();
}
// 重置模型轴方向
function initManyModelRotation(uuid) {
const manyModel = this.scene.getObjectByProperty('uuid', uuid)
if (!manyModel) return
const { userData: { rotation } } = manyModel
manyModel.rotation.set(rotation.x, rotation.y, rotation.z)
}
//设置模型位置
function setManyModelPosition(position, uuid) {
const manyModel = this.scene.getObjectByProperty('uuid', uuid)
if (!manyModel) return
const Tween = new TWEEN.Tween(manyModel.position)
const endPosition = {
x: position.x,
y: position.y,
z: position.z
}
Tween.to(endPosition, 500)
Tween.onUpdate((val) => {
manyModel.position.set(val.x || 0, val.y || 0, val.z || 0)
})
Tween.start();
}
// 重置模型位置
function initManyModelPosition(uuid) {
const manyModel = this.scene.getObjectByProperty('uuid', uuid)
const { userData: { position } } = manyModel
manyModel.position.set(position.x || 0, position.y || 0, position.z || 0)
return {
...position
}
}
// 设置模型缩放
function setManyModelScale(uuid, scale) {
const manyModel = this.scene.getObjectByProperty('uuid', uuid)
if (!manyModel) return
const Tween = new TWEEN.Tween(manyModel.scale)
const endPosition = {
x: scale,
y: scale,
z: scale
}
Tween.to(endPosition, 500)
Tween.onUpdate((val) => {
manyModel.scale.set(val.x || 0, val.y || 0, val.z || 0)
})
Tween.start();
}
结语
注意:因为考虑到多模型加载带来性能问题,目前项目中只针对了项目内的模型进行拖拽添加,无法加载外部多模型
以上就是多模型编辑实现的核心代码,完整的代码示例可参考:
github:github.com/zhangbo126/…
gitee:gitee.com/ZHANG_6666/…
转载自:https://juejin.cn/post/7404040099643654184