案例展示
改变模型材质
<template>
<div class="vp-px2vw">
<div class="demo-wrap">
<div id="TO"></div>
<div id="formContainer">
<ModelMaterialForm
:mesh-cascader-options="meshCascaderOptions"
@apply-material="handleMaterialChange" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
// @ts-ignore
declare const ThingOrigin: any
import { ref, onMounted } from 'vue'
import ModelMaterialForm, {
type MaterialApplyPayload,
type MeshCascaderLeaf,
type MeshCascaderOption
} from './ModelMaterialForm.vue'
const meshCascaderOptions = ref<MeshCascaderOption[]>([])
let TO: any = null
let modelList = [
{
modelType: 'gltf',
modelName: 'ABB机器人-1',
id: 3012,
position: {
x: 0,
y: 0,
z: 0
},
base: {
modelUrl: 'https://124.70.30.193:8443/model2/loadFileAsId/56201403'
}
},
{
modelType: 'gltf',
modelName: '非皮带传送带-1',
id: 3021,
position: {
x: -24.90687943165758,
y: 0,
z: 19.922502148523417
},
base: {
modelUrl: 'https://124.70.30.193:8443/model2/loadFileAsId/258642254'
}
},
{
modelType: 'gltf',
modelName: '非皮带传送带-2',
id: 3021,
position: {
x: 40.90687943165758,
y: 0,
z: 19.922502148523417
},
base: {
modelUrl: 'https://124.70.30.193:8443/model2/loadFileAsId/258642254'
}
},
{
modelType: 'gltf',
modelName: '精雕机床-1',
id: 3016,
position: {
x: -6,
y: 0,
z: -23
},
rotation: {
x: 0,
y: 3.14,
z: 0
},
base: {
modelUrl: 'https://124.70.30.193:8443/model2/loadFileAsId/701730674'
}
},
{
modelType: 'gltf',
modelName: '数字机床-1',
id: 3014,
position: {
x: 46,
y: 0,
z: -4
},
rotation: {
x: 3.14,
y: 0,
z: 3.14
},
base: {
modelUrl: 'https://124.70.30.193:8443/model2/loadFileAsId/878966042'
}
}
]
onMounted(async () => {
initScene()
})
function initScene() {
//初始化场景
TO = new ThingOrigin(
'fileModel',
document.getElementById('TO'),
{
scene: {
ground: {
//设置地面
active: false
},
background: {
type: 'sky'
},
renderQuality: {
toneMapping: {
exposure: 0.8 //曝光度
}
}
},
camera: {
//相机参数
lookAt: {
x: 25.62454680418967,
y: -2.3106037456621856,
z: -66.99965038950995
},
position: {
x: 41.87976418982612,
y: 78.50564268979477,
z: 117.31701183506453
}
},
modelList: modelList
},
(TO: any) => {
console.log('模型加载完成')
buildMeshCascaderOptions()
}
)
}
//级联项文案:仅当前节点名称,无名则用类型 + uuid 前缀
function nodeDisplayLabel(obj: any): string {
const n = obj.name?.trim()
if (n) return n
const t = obj.type ?? 'Object3D'
return `(${t})_${obj.uuid?.slice(0, 8) ?? '?'}`
}
//构建材质级联选项
function buildMeshCascaderOptions() {
if (!TO) return
const options: MeshCascaderOption[] = []
for (const item of modelList) {
const root = getModel(item.modelName)
const children: MeshCascaderLeaf[] = []
if (root) {
root.traverse((child: any) => {
if (child.isMesh) {
children.push({
value: child.uuid,
label: nodeDisplayLabel(child)
})
}
})
}
options.push({
value: item.modelName,
label: item.modelName,
children
})
}
meshCascaderOptions.value = options
}
//查找模型中的Mesh
function findMeshInModel(modelName: string, meshUuid: string): any | null {
const root = getModel(modelName)
if (!root) return null
let found: any = null
root.traverse((child: any) => {
if (child.isMesh && child.uuid === meshUuid) found = child
})
return found
}
// 应用材质到 Mesh(数值由子组件 emit 的 payload 提供)
function applyMaterialToMesh(mesh: any, payload: MaterialApplyPayload) {
const mats = Array.isArray(mesh.material) ? mesh.material : [mesh.material]
for (const mat of mats) {
if (!mat) continue
if (mat.color !== undefined) mat.color.set(payload.color)
if (mat.metalness !== undefined) mat.metalness = payload.metalness
if (mat.roughness !== undefined) mat.roughness = payload.roughness
}
}
// 只对级联选中的 Mesh 生效(两级:[模型名, meshUuid])
function handleMaterialChange(payload: MaterialApplyPayload) {
const path = payload.cascaderPath
if (!path || path.length !== 2) {
return
}
const [modelName, meshUuid] = path
const mesh = findMeshInModel(modelName, meshUuid)
if (mesh) {
applyMaterialToMesh(mesh, payload)
} else {
console.log('未找到 Mesh', modelName, meshUuid)
}
}
/*
* 根据模型名称获取场景中的模型对象
*/
function getModel(modelName: string) {
if (!TO) {
return undefined
}
return TO.scene.getObjectByName(modelName)
}
</script>
<style scoped lang="scss">
.vp-px2vw {
.demo-wrap {
position: relative;
}
#TO {
width: 100%;
height: 400px;
position: relative;
}
#formContainer {
position: absolute;
top: 10px;
right: 10px;
font-size: 14px;
}
}
</style>