three.js多模型居中

目录

前言

案例

 加载模型

分析问题

解决思路

最终代码


前言

        我们使用three时候,大多数都是加载外部的模型的,这样可以配合模型编辑软件合作,从而高质量完成项目(three内置的模型就那么点,能展示啥呢?)。有时候我们不经好奇,为什么模型加载之后直接放入到场景中不是在坐标中心而是有自己的位置呢?明明自己没有给mesh设置位置,这是因为建模师在编辑该模型时候并没有在坐标中心制作模型的,所以就会有这种现象。

        所以问题来了,建模师给我们的模型都有自己的位置,而且还不知道有多少,我们不仅需要正确放置他们的相对位置,也要让所有模型整体居中,我们该如何实现呢?

案例

        说理论还不如实操好,这里我就搭建一个环境试验一下,业务需求是这样的:后端传入stl人体器官模型,前端居中加载。(人体器官的位置可就不能乱放了,你也不想肾脏在头顶吧?)

 加载模型

<template>
  <div class="container" ref="container">

  </div>
</template>

<script setup>
import ThreeManager from '@/js/ThreeManager';
import { onBeforeUnmount, onMounted, ref } from 'vue';
import * as THREE from "three";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";

const container = ref()
let threeObj
const fileList = [
  {
    path: "/STL/动脉.stl"
  },
  {
    path: "/STL/病灶.stl"
  },
  {
    path: "/STL/胆囊胆管.stl"
  },
  {
    path: "/STL/肝静脉.stl"
  },
  {
    path: "/STL/肝脏.stl"
  },
  {
    path: "/STL/淋巴结.stl"
  },
  {
    path: "/STL/门静脉.stl"
  },
  {
    path: "/STL/肾脏.stl"
  },
  {
    path: "/STL/下腔静脉.stl"
  }
]
onMounted(() => {
  threeObj = new ThreeManager(container.value)
  for (let i = 0; i < fileList.length; i++) {
    const loader = new STLLoader()
    loader.load(fileList[i].path, function (geometry) {
      console.log(geometry);
      const material = new THREE.MeshPhongMaterial({
        color: "#e5a83d",
        opacity: 1,
        transparent: true,
        visible: true,
        depthTest: true,
      })
      const mesh = new THREE.Mesh(geometry, material);
      mesh.position.set(0, 0, 0);
      mesh.rotation.set(-Math.PI / 2, 0, 0);
      threeObj.add(mesh)
      threeObj.add(new THREE.AmbientLight(0x444444))
      // mesh.castShadow = true
      // mesh.receiveShadow = true
      // resolve(mesh)
    })
  }
  // threeObj.add(cube)
})
onBeforeUnmount(() => {
  threeObj.destory()
  threeObj = null
})
</script>

<style scoped>
.container {
  width: 100%;
  height: 100%;
}
</style>

 (源码和效果如上)


      mesh.position.set(0, 0, 0);
      mesh.rotation.set(-Math.PI / 2, 0, 0);

        下面两行代码是对所有模型进行一个调整,让我们打开three页面时候,器官是对着我们自己的,上面效果图其实是蓝色轴正对着自己的,我移动了一下,好展示出效果。


threeObj = new ThreeManager(container.value)

        这个ThreeManager类内部只是创建了一个场景,然后把画布渲染到传入构造函数的元素中,并没有什么特别的地方。

分析问题

        基本环境搭建好了,问题就浮现了,模型加载并没有在坐标轴中心,如果客户想进入页面第一眼就可以看到居中的模型怎么办?而且后续不仅仅只有上图那么点模型,以后还会有胸骨,头颅怎么办,怎么动态整合所有模型,然后调整位置到坐标轴上呢?

解决思路

        不知道大家有没有了解过THREE.Object3D类,他是可以将所有模型添加到一个变量上,然后在利用scene的add函数将多个模型整体添加到场景中,而不用像我们这样一个一个添加,况且,我们还可以利用THREE.Object3D()进行模型分组,可以同时管理多个模型(当然我们现在不关注这个),我们可以利用他来计算出所有模型的整体中心点位置,有了中心点,我们就可以动态的将后端返回的任意多个模型放置到坐标轴上了。所以我们先将代码修改如下:

<template>
  <div class="container" ref="container">

  </div>
</template>

<script setup>
import ThreeManager from '@/js/ThreeManager';
import { onBeforeUnmount, onMounted, ref } from 'vue';
import * as THREE from "three";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";

const container = ref()
let threeObj
const fileList = [
  {
    path: "/STL/动脉.stl"
  },
  {
    path: "/STL/病灶.stl"
  },
  {
    path: "/STL/胆囊胆管.stl"
  },
  {
    path: "/STL/肝静脉.stl"
  },
  {
    path: "/STL/肝脏.stl"
  },
  {
    path: "/STL/淋巴结.stl"
  },
  {
    path: "/STL/门静脉.stl"
  },
  {
    path: "/STL/肾脏.stl"
  },
  {
    path: "/STL/下腔静脉.stl"
  }
]
onMounted(async () => {
  threeObj = new ThreeManager(container.value)
  const group = new THREE.Object3D()
  for (let i = 0; i < fileList.length; i++) {
    const loader = new STLLoader()
    const mesh = await new Promise((resolve, reject) => {
      loader.load(fileList[i].path, function (geometry) {
        const material = new THREE.MeshPhongMaterial({
          color: "#e5a83d",
          opacity: 1,
          transparent: true,
          visible: true,
          depthTest: true,
        })
        const mesh = new THREE.Mesh(geometry, material);
        mesh.position.set(0, 0, 0);
        mesh.rotation.set(-Math.PI / 2, 0, 0);
        resolve(mesh)
        // group.add(mesh)

        // mesh.castShadow = true
        // mesh.receiveShadow = true
        // resolve(mesh)
      })
    })
    group.add(mesh)
  }
  threeObj.add(group)
  threeObj.add(new THREE.AmbientLight(0x444444))
  // threeObj.add(cube)
})
onBeforeUnmount(() => {
  threeObj.destory()
  threeObj = null
})
</script>

<style scoped>
.container {
  width: 100%;
  height: 100%;
}
</style>

    const mesh = await new Promise((resolve, reject) => {
      loader.load(fileList[i].path, function (geometry) {
        const material = new THREE.MeshPhongMaterial({
          color: "#e5a83d",
          opacity: 1,
          transparent: true,
          visible: true,
          depthTest: true,
        })
        const mesh = new THREE.Mesh(geometry, material);
        mesh.position.set(0, 0, 0);
        mesh.rotation.set(-Math.PI / 2, 0, 0);
        resolve(mesh)
        // group.add(mesh)

        // mesh.castShadow = true
        // mesh.receiveShadow = true
        // resolve(mesh)
      })
    })

        这里为什么要使用promise呢?是因为这个模型加载是发送网络请求获取模型的,是异步的,所以需要我们用promise装修一下,不然scene可能获取不到模型,加了个空的group上去。

所以效果如下:

        没错问题还是这样,那么我们怎么计算这个group也就是多个模型整体的中心点呢?

最终代码

         我们将代码修改成如下:

<template>
  <div class="container" ref="container">

  </div>
</template>

<script setup>
import ThreeManager from '@/js/ThreeManager';
import { onBeforeUnmount, onMounted, ref } from 'vue';
import * as THREE from "three";
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";

const container = ref()
let threeObj
const fileList = [
  {
    path: "/STL/动脉.stl"
  },
  {
    path: "/STL/病灶.stl"
  },
  {
    path: "/STL/胆囊胆管.stl"
  },
  {
    path: "/STL/肝静脉.stl"
  },
  {
    path: "/STL/肝脏.stl"
  },
  {
    path: "/STL/淋巴结.stl"
  },
  {
    path: "/STL/门静脉.stl"
  },
  {
    path: "/STL/肾脏.stl"
  },
  {
    path: "/STL/下腔静脉.stl"
  }
]
onMounted(async () => {
  threeObj = new ThreeManager(container.value)
  const group = new THREE.Object3D()
  for (let i = 0; i < fileList.length; i++) {

    const loader = new STLLoader()
    const mesh = await new Promise((resolve, reject) => {
      loader.load(fileList[i].path, function (geometry) {
        // geometry.computeBoundingBox()
        const material = new THREE.MeshPhongMaterial({
          color: "#e5a83d",
          opacity: 1,
          transparent: true,
          visible: true,
          depthTest: true,
        })
        const mesh = new THREE.Mesh(geometry, material);
        mesh.position.set(0, 0, 0);
        mesh.rotation.set(-Math.PI / 2, 0, 0);
        resolve(mesh)
        // group.add(mesh)

        // mesh.castShadow = true
        // mesh.receiveShadow = true
        // resolve(mesh)
      })
    })
    group.add(mesh)

  }
  var box = new THREE.Box3();
  const groupBox = box.expandByObject(group)
  // 通过传入的object3D对象来返回当前模型的最小大小,值可以使一个mesh也可以使group
  const centerX = (groupBox.max.x + groupBox.min.x) / 2
  const centerY = (groupBox.max.y + groupBox.min.y) / 2
  const centerZ = (groupBox.max.z + groupBox.min.z) / 2
  group.position.set(-centerX, -centerY, -centerZ)
  threeObj.add(group)
  threeObj.add(new THREE.AmbientLight(0x444444))
  // threeObj.add(cube)
})
onBeforeUnmount(() => {
  threeObj.destory()
  threeObj = null
})
</script>

<style scoped>
.container {
  width: 100%;
  height: 100%;
}
</style>

 模型居中了,所以我们干了啥?...

  var box = new THREE.Box3();
  const groupBox = box.expandByObject(group)

上面的代码利用box3类,的expandByObject方法,可以计算出多模型整体group的边界向量,什么意思呢?

我们先打印一下这个groupBox是什么:

         这个max就是模型整体离坐标轴中心最远的点的向量,min就是模型整体离坐标轴中心最近的点的向量(我还是先解释一下变量是啥吧,我也是最近才知道的,读者学过但是可能忘了)

        

 图片如上(有些粗糙见谅...)

        这张图相当于scene场景中有一个棍子模型(AB),我们想让这根棍子中心点移动到坐标轴原点上,根据上面的groupBox可知我们相当于已经知道了A点(最近)B点(最远)的两个向量0A,0B,而我们如果计算出中心点的向量是不是相当于知道了,而模型的中心点向量等于(0A-0B)/2。在项目中相当于:

  const centerX = (groupBox.max.x + groupBox.min.x) / 2
  const centerY = (groupBox.max.y + groupBox.min.y) / 2
  const centerZ = (groupBox.max.z + groupBox.min.z) / 2

然后在移动模型group:

group.position.set(-centerX, -centerY, -centerZ)

完美收工... 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值