three.js相机按照指定路线在建筑模型中漫游(支持开始,暂停)

three.js相机按照指定路线在模型中漫游(支持开始,暂停)

关键点

相机运动曲线

javascript 复制代码
// 相机路线
const points = [
  new THREE.Vector3(0, 40, 300),
  new THREE.Vector3(50, 40, 300),
  new THREE.Vector3(50, 40, 50),
  new THREE.Vector3(150, 40, 50),
  new THREE.Vector3(150, 40, 0),
];
// 三维样条曲线
const path = new THREE.CatmullRomCurve3(points);
// 从曲线上等间距获取一定数量点坐标,点越多,相机运动越慢
const pointsArr = path.getSpacedPoints(2500);

相机位置移动

javascript 复制代码
    camera.position.copy(pointsArr[i]);
    // 曲线上当前点pointsArr[i]和下一个点pointsArr[i+1]近似模拟当前点曲线切线
    // 设置相机观察点为当前点的下一个点,相机视线和当前点曲线切线重合
    camera.lookAt(pointsArr[i + 1]);
    i += 1; //调节速度

完整代码

javascript 复制代码
<template>
  <div class="app">
    <div class="btns">
      <button @click="btnStart">开始移动</button>
      <button @click="btnStop">暂停</button>
    </div>
    <div ref="canvesRef" class="canvas-wrap"></div>
  </div>
</template>

<script setup>
import { ref, onMounted } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import TWEEN from "@tweenjs/tween.js";

const canvesRef = ref(null);
const canvasWidth = window.innerWidth;
const canvasHeight = window.innerHeight;
const isPlay = ref(false);
let stopPoint = new THREE.Vector3(0, 40, 300);

let scene;
let camera;
let renderer;
let axesHelper;
let cameraControls;
// 相机路线
const points = [
  new THREE.Vector3(0, 40, 300),
  new THREE.Vector3(50, 40, 300),
  new THREE.Vector3(50, 40, 50),
  new THREE.Vector3(150, 40, 50),
  new THREE.Vector3(150, 40, 0),
];
// 路径曲线
const pathPoints = [
  new THREE.Vector3(0, 0, 300),
  new THREE.Vector3(50, 0, 300),
  new THREE.Vector3(50, 0, 50),
  new THREE.Vector3(150, 0, 50),
  new THREE.Vector3(150, 0, 0),
];
// 三维样条曲线
const path = new THREE.CatmullRomCurve3(points);
// 从曲线上等间距获取一定数量点坐标,点越多,相机运动越慢
const pointsArr = path.getSpacedPoints(2500);
// 场景
scene = new THREE.Scene();
// 模型
for (let i = 0; i < 3; i++) {
  for (let j = 0; j < 3; j++) {
    const geometry = new THREE.BoxGeometry(40, 80, 40);
    const material = new THREE.MeshBasicMaterial({
      color: getRandomColor(),
    });
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.set(i * 100, 40, j * 100);
    scene.add(mesh);
  }
}
renderPath();
// 相机
camera = new THREE.PerspectiveCamera(75, canvasWidth / canvasHeight, 0.1, 3000);
camera.position.set(400, 400, 400);
camera.lookAt(0, 0, 0);

// 坐标辅助对象
axesHelper = new THREE.AxesHelper(200);
scene.add(axesHelper);

// 渲染器
renderer = new THREE.WebGLRenderer();
renderer.setSize(canvasWidth, canvasHeight);

// 动画渲染循环
let i = 0;
function animate() {
  if (isPlay.value && i < pointsArr.length - 1) {
    // 相机位置设置在当前点位置
    camera.position.copy(pointsArr[i]);
    // 曲线上当前点pointsArr[i]和下一个点pointsArr[i+1]近似模拟当前点曲线切线
    // 设置相机观察点为当前点的下一个点,相机视线和当前点曲线切线重合
    camera.lookAt(pointsArr[i + 1]);
    i += 1; //调节速度
  }
  if (i === pointsArr.length - 1) {
    camera.position.set(400, 400, 400);
    camera.lookAt(0, 0, 0);
  }
  renderer.render(scene, camera);
  TWEEN.update();

  requestAnimationFrame(animate);
}
animate();
// 相机轨道控制器
cameraControls = new OrbitControls(camera, renderer.domElement);
cameraControls.target.set(0, 0, 0); // 与相机的lookat保持一致
cameraControls.maxPolarAngle = Math.PI / 2; // 相机不能进入地下

onMounted(() => {
  canvesRef.value.appendChild(renderer.domElement);
});
// 随机色16进制
function getRandomColor() {
  var letters = "0123456789ABCDEF";
  var color = "#";
  for (var i = 0; i < 6; i++) {
    color += letters[Math.floor(Math.random() * 16)];
  }
  return color;
}
// 开始
function btnStart() {
  console.log("开始");
  // 相机动画
  new TWEEN.Tween(camera.position)
    .to(stopPoint, 2000)
    .onUpdate(function () {
      // camera.lookAt(0, 0, 0);
      camera.lookAt(stopPoint.x, stopPoint.y, stopPoint.z);
    })
    .onComplete(function () {
      isPlay.value = true;
    })
    .start();
}
function btnStop() {
  stopPoint = camera.position.clone();
  isPlay.value = false;
}
function renderPath() {
  const material = new THREE.LineBasicMaterial({
    color: 0x0000ff,
  });

  const geometry = new THREE.BufferGeometry().setFromPoints(pathPoints);

  const line = new THREE.Line(geometry, material);

  scene.add(line);
}
</script>

<style lang="scss" scoped>
.app {
  position: relative;
  .btns {
    position: absolute;
    top: 20px;
    left: 50px;
  }
}
</style>

图例

相关推荐
lpfasd12311 小时前
node-sass 停止维护!老项目迁移 Dart Sass 完整教程
前端·rust·sass
Data analyse45613 小时前
rnweb 回放数据量太大怎么办?分片存储与断点续传的实现思路
前端·网络·算法·数据分析
xiaogai_gai13 小时前
金蝶收料通知单查询策略实战教程:从源系统拉取到中间层落地的完整路径
java·前端·数据库
环境栈笔记14 小时前
指纹浏览器 WebRTC 怎么设置?从 ICE 候选、代理出口到功能验证
前端·人工智能·后端·自动化
赵大仁14 小时前
官网性能优化实战:LCP 3.2s → 2.1s、CLS 0.107 → 0,以及被我推翻的 6 个判断
前端·性能优化·cls·lcp·core web vitals
xiancai_xianyu14 小时前
Text2SQL落地生产库前,SQL要过哪三道闸
前端·数据库·sql·sql注入·text2sql·ai agent开发·企业级ai框架
小羊没烦恼!15 小时前
T-SQL查询进阶--理解SQL SERVER中的分区表
服务器·开发语言·前端·javascript·html
史瓦辛格15 小时前
给 DeepSeek Harness 写费用插件:把「这次会话花了多少钱」算准
前端·deepseek
星光不问赶路人15 小时前
Vue 3 + TypeScript 中 enum 的使用实践
前端·vue.js
鸥梨菌Honevid15 小时前
UVM 中 REQ 和 RSP 类型详解:从通信模型到 item_done 与 put_response
java·前端·servlet