基于 Vue3 + Three.js 实现老人模型沿路径行走动画系统

本项目基于 Three.js + Vue3 搭建了一个三维场景交互平台,实现了老人模型在指定路径上漫游可视化路径编辑高质量贴图渲染等功能,支持动态路径绘制动画播放控制

核心功能模块

  • 初始化 Scene、Camera、Renderer、Controls。

  • 设置 OrbitControls 允许用户缩放、旋转、查看。

  • 加载光照系统(Ambient、Directional、Point Light)保证模型亮度。

  • 加载地板(PlaneGeometry)用于鼠标点击拾取路径点。

  • 支持动态贴图(360度全景图转换为立方体贴图)作为环境背景和反射环境。

  • 通过 GLTFLoader 加载 scene.gltf 场景模型。

  • 自动计算模型的包围盒,进行居中、缩放、归一化处理。

  • 动态调整相机位置和控制器范围以适配模型尺寸。

 

  • 通过 GLTFLoader 加载动画模型 scene.gltf

  • 使用 AnimationMixer 播放动作(默认选择动画名称为 'Take 001')。

  • 设置缩放比例并将模型加入场景。

 

鼠标路径点拾取与曲线绘制 onMouseClick() + updateCurve()

  • 支持点击地板获取点击位置坐标,生成路径点。

  • 使用 CatmullRomCurve3 创建平滑曲线。

  • 将路径可视化为绿色线段,同时保留路径点球体标识。

  • 自动更新路径线(上一次路径被销毁释放资源)

    鼠标路径点拾取与曲线绘制 onMouseClick() + updateCurve()

  • 支持点击地板获取点击位置坐标,生成路径点。

  • 使用 CatmullRomCurve3 创建平滑曲线。

  • 将路径可视化为绿色线段,同时保留路径点球体标识。

  • 自动更新路径线(上一次路径被销毁释放资源)

 

沿路径自动漫游动画 animate()

  • 使用 getPointAt(t) 计算路径位置,oldMan.position.copy() 实现移动。

  • 使用 lookAt(nextPos) 保证朝向平滑过渡。

  • 结合 Tween.js 实现更复杂的平滑移动也预留了空间(tweenGroup)。

 

功能类别描述
✅ 场景搭建Renderer + Camera + Scene + Controls 标准架构,支持自适应大小
✅ 模型加载支持场景模型和动画人物模型的 GLTF 加载、归一化、居中
✅ 动画系统支持 AnimationMixer 播放 GLTF 动画
✅ 路径绘制支持点击拾取路径点,自动生成 CatmullRom 曲线并渲染
✅ 人物漫游沿着曲线路径自动移动,支持方向自动面向下一个点
✅ 环境贴图360° equirectangular 环境贴图转立方体贴图,提升渲染质量
✅ 响应式支持自适应窗口尺寸变动,支持窗口重绘
✅ 性能优化清理旧资源(曲线材质与几何体)、帧同步动画、透明地板遮盖

 

 

<template>
  <div ref="threeCanvas" style="width: 100%; height: 100%" id="c"></div>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import * as THREE from 'three';
import { Tween, Group } from '@tweenjs/tween.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
//catmullRomcurve3生成平滑的曲线路径
import { CatmullRomCurve3 } from 'three';


const threeCanvas = ref(null);
// 初始化renderer,scene、camera、controls
let renderer, scene, camera, controls;
// 用来控制时间的控制开始播放动画时,Clock 开始“计时从上次更新到现在过了多少秒?” 它会告诉你一个 delta(增量)值elta 可以用来控制物体运动的速度
const clock = new THREE.Clock();
// 控制模型动画播放
let mixer = null;
// 老人模型对象
let oldMan = null;
// 管理动画播放的组 像是一个文件夹里面可以放很多 3D 物体你给这个文件夹移动、旋转、缩放,里面所有的物体都会一起动
const tweenGroup = new Group();

let model = null;           // 场景模型
// 用于鼠标拾取射线检测
// 监听鼠标事件,得到鼠标的像素坐标(例如X=500,Y=300)

// 把这个像素坐标转换成mouse变量的规范化坐标(-1到1)

// 用 raycaster 从摄像机位置往 mouse 指定方向射出一条射线

// 检测这条射线碰到了哪些模型(model)

// 返回碰到的模型,告诉你“你点中了它”
const raycaster = new THREE.Raycaster();
// 储存鼠标屏幕坐标
const mouse = new THREE.Vector2();

// const pathPoints = [
//    new THREE.Vector3(6.4196, -1.5316, 2.1559),
//   new THREE.Vector3(6.4171, -1.5316, 1.6201),
//   new THREE.Vector3(6.4298, -1.5316, 1.5201),
//   new THREE.Vector3(6.4068, -1.5316, 1.2091),
//   new THREE.Vector3(6.4208, -1.5316, 0.9920),
//   new THREE.Vector3(6.4490, -1.5316, 0.6572),
//   new THREE.Vector3(6.3904, -1.5316, 0.2512),
//   new THREE.Vector3(6.4287, -1.5316, 0.0442),
//   new THREE.Vector3(6.4705, -1.5316, -0.2997),
//   new THREE.Vector3(6.4657, -1.5316, -0.4175),
//   new THREE.Vector3(6.4467, -1.5316, -0.5084),
//   new THREE.Vector3(6.4344, -1.5316, -0.6307),
//   new THREE.Vector3(4.7067, -1.5316, -0.7048),
//   new THREE.Vector3(4.2222, -1.5316, -0.7167),
//   new THREE.Vector3(3.5638, -1.5316, -0.7867),
//   new THREE.Vector3(2.5262, -1.5316, -0.9241),
//   new THREE.Vector3(1.3211, -1.5316, -0.9406),
//   new THREE.Vector3(0.7452, -1.5254, -1.6667),
//   new THREE.Vector3(0.8206, -1.5316, -3.2251),
//   new THREE.Vector3(0.7029, -1.1531, -3.5734),
//   new THREE.Vector3(0.6420, -1.1592, -3.4076),
//   new THREE.Vector3(0.3622, -1.5317, -4.1282),
//   new THREE.Vector3(0.3225, -1.5317, -4.9323),
//   new THREE.Vector3(0.2781, -1.5317, -4.8699),
//   new THREE.Vector3(0.2801, -1.5317, -4.7799),
//   new THREE.Vector3(0.2710, -1.5317, -4.6158),
//   new THREE.Vector3(0.2277, -1.5317, -4.4683),
//   new THREE.Vector3(0.1468, -1.5317, -4.2639),
//   new THREE.Vector3(0.0767, -1.5317, -4.1329),
//   new THREE.Vector3(-0.0036, -1.5316, -3.9601),
//   new THREE.Vector3(-0.3682, -1.5316, -3.7575),
//   new THREE.Vector3(-0.3908, -1.5316, -3.7285),
//   new THREE.Vector3(-0.5117, -1.5316, -3.7072),
//   new THREE.Vector3(-0.4032, -1.5316, -3.5493),
//   new THREE.Vector3(-0.2820, -1.5316, -3.4594),
//   new THREE.Vector3(-0.0769, -1.5316, -3.2622),
//   new THREE.Vector3(0.1811, -1.5316, -3.1384),
//   new THREE.Vector3(0.3936, -1.5316, -3.1541),
//   new THREE.Vector3(0.5692, -1.5316, -2.8172),
//   new THREE.Vector3(0.5906, -1.5254, -2.5948),
//   new THREE.Vector3(0.6138, -1.5254, -2.2521),
//   new THREE.Vector3(0.6698, -1.5254, -1.9111),
//   new THREE.Vector3(0.7027, -1.5254, -1.5886),
//   new THREE.Vector3(0.7017, -1.5254, -1.5627),
//   new THREE.Vector3(0.8371, -1.5254, -1.4829),
//   new THREE.Vector3(0.8549, -1.5254, -1.3437),
//   new THREE.Vector3(0.9120, -1.5316, -1.2007),
//   new THREE.Vector3(0.9152, -1.2936, -1.0072),
//   new THREE.Vector3(0.9257, -1.2940, -0.9854),
//   new THREE.Vector3(1.0052, -1.2760, -0.9851),
//   new THREE.Vector3(2.3214, -1.5316, -0.9415),
//   new THREE.Vector3(2.5338, -1.5316, -0.9500),
//   new THREE.Vector3(3.0566, -1.5316, -0.9201),
//   new THREE.Vector3(3.1610, -1.4302, -2.6449),
//   new THREE.Vector3(3.1557, -1.5217, -3.4080),
//   new THREE.Vector3(3.1883, -1.5217, -3.9457),
//   new THREE.Vector3(2.7172, -1.5217, -4.0546),
//   new THREE.Vector3(2.2665, -1.5217, -4.0999),
//   new THREE.Vector3(2.3441, -1.5317, -4.8175),
//   new THREE.Vector3(1.7710, -0.2167, -4.0579),
//   new THREE.Vector3(2.1026, -1.5317, -4.4224),
//   new THREE.Vector3(2.1063, -1.5317, -4.2882),
//   new THREE.Vector3(2.1253, -1.5317, -4.2743),
//   new THREE.Vector3(2.2161, -1.5317, -4.1844),
//   new THREE.Vector3(2.4674, -0.9122, -4.0579),
//   new THREE.Vector3(2.6245, -1.1370, -4.0619),
//   new THREE.Vector3(2.8020, -1.3726, -4.0619),
//   new THREE.Vector3(2.8789, -1.5217, -3.9944),
//   new THREE.Vector3(2.9393, -1.5217, -3.8445),
//   new THREE.Vector3(2.9765, -1.5218, -3.6816),
//   new THREE.Vector3(3.0671, -1.5217, -3.0895),
//   new THREE.Vector3(3.0776, -1.5227, -2.8732),
//   new THREE.Vector3(2.9843, -1.5217, -2.5719),
//   new THREE.Vector3(3.0975, -1.5217, -2.4632),
//   new THREE.Vector3(3.2010, -1.0086, -2.2587),
//   new THREE.Vector3(3.2422, -1.0207, -2.1944),
//   new THREE.Vector3(3.2743, -0.9951, -2.1142),
//   new THREE.Vector3(3.3982, -1.4303, -2.1759),
//   new THREE.Vector3(3.0181, -1.5316, -1.0897),
//   new THREE.Vector3(3.1544, -1.5316, -0.9389),
//   new THREE.Vector3(4.7340, -1.5316, -0.9179),
//   new THREE.Vector3(4.8870, -1.0513, -1.0009),
//   new THREE.Vector3(5.9282, -1.5316, -1.3546),
//   new THREE.Vector3(6.4366, -1.5316, -1.7501),
//   new THREE.Vector3(6.8494, -0.9751, -2.0724),
//   new THREE.Vector3(7.6253, -1.5199, -2.7929),
//   new THREE.Vector3(7.4687, -1.5199, -3.5638),
//   new THREE.Vector3(7.3276, -1.5199, -3.9571),
//   new THREE.Vector3(7.0754, -1.5317, -4.3921),
//   new THREE.Vector3(5.8808, 0.5383, -3.9948)
// ];

// 鼠标点击采集的路径点
let curve = null;
const pathPoints = [
  new THREE.Vector3(6.319822, -1.53, 2.570512),
  new THREE.Vector3(6.406049, -1.53, 2.164301),
  new THREE.Vector3(6.359590, -1.53, 2.039799),
  new THREE.Vector3(6.443645, -1.53, 1.595450),
  new THREE.Vector3(6.352108, -1.53, 1.354298),
  new THREE.Vector3(6.391970, -1.53, 0.996318),
  new THREE.Vector3(6.433369, -1.53, 0.818301),
  new THREE.Vector3(6.430238, -1.53, 0.583670),
  new THREE.Vector3(6.427887, -1.53, 0.401656),
  new THREE.Vector3(6.423045, -1.53, 0.021679),
  new THREE.Vector3(6.438557, -1.53, -0.426365),
  new THREE.Vector3(6.435172, -1.53, -0.684745),
  new THREE.Vector3(6.705428, -1.53, -0.858215),
  new THREE.Vector3(7.182494, -1.53, -0.869206),
  new THREE.Vector3(7.559753, -1.53, -0.875711),
  new THREE.Vector3(7.720775, -1.53, -1.036134),
  new THREE.Vector3(7.679018, -1.53, -1.771417),
  new THREE.Vector3(7.679299, -1.53, -2.556160),
  new THREE.Vector3(7.561710, -1.53, -3.398952),
  new THREE.Vector3(7.422641, -1.53, -3.881184),
  new THREE.Vector3(6.792596, -1.53, -3.856359),
  new THREE.Vector3(6.216405, -1.53, -3.869296),
  new THREE.Vector3(6.258762, -1.53, -3.603863),
  new THREE.Vector3(6.079989, -1.53, -3.468030),
  new THREE.Vector3(5.963924, -1.53, -3.793249),
  new THREE.Vector3(5.719277, -1.53, -3.779842),
  new THREE.Vector3(5.319093, -1.53, -3.748659),
  new THREE.Vector3(4.677778, -1.53, -3.702489),
  new THREE.Vector3(4.412901, -1.53, -3.611398),
  new THREE.Vector3(4.292174, -1.53, -3.246777),
  new THREE.Vector3(4.070667, -1.53, -2.918138),
  new THREE.Vector3(4.044400, -1.53, -2.482111),
  new THREE.Vector3(3.984729, -1.53, -1.835359),
  new THREE.Vector3(3.995187, -1.53, -1.318142),
  new THREE.Vector3(4.059139, -1.53, -0.980950),
  new THREE.Vector3(4.143814, -1.53, -0.617863),
  new THREE.Vector3(3.349554, -1.53, -0.824308),
  new THREE.Vector3(3.043609, -1.53, -0.543691),
  new THREE.Vector3(2.488461, -1.53, -0.440182),
  new THREE.Vector3(2.009007, -1.53, -0.479957),
  new THREE.Vector3(1.568039, -1.53, -0.374817),
  new THREE.Vector3(1.358505, -1.53, -0.426029),
  new THREE.Vector3(0.633736, -1.53, -0.400807),
  new THREE.Vector3(0.313731, -1.53, -0.553517),
  new THREE.Vector3(0.509338, -1.53, -1.300437),
  new THREE.Vector3(0.507492, -1.53, -2.494333),
  new THREE.Vector3(-0.112694, -1.53, -5.047881),
  new THREE.Vector3(0.368581, -1.53, -4.402690),
  new THREE.Vector3(0.563918, -1.53, -3.301067),
  new THREE.Vector3(0.755048, -1.53, -2.809569),
  new THREE.Vector3(0.926919, -1.53, -1.707784),
  new THREE.Vector3(0.915208, -1.53, -0.997344),
  new THREE.Vector3(1.102943, -1.53, -0.880010),
  new THREE.Vector3(1.595748, -1.53, -0.692275),
  new THREE.Vector3(2.112019, -1.53, -0.856543),
  new THREE.Vector3(2.745625, -1.53, -0.880010),
  new THREE.Vector3(3.237771, -1.53, -0.841401),
  new THREE.Vector3(3.228425, -1.53, -1.573625),
  new THREE.Vector3(3.231418, -1.53, -1.926240),
  new THREE.Vector3(2.977760, -1.53, -2.492462),
  new THREE.Vector3(2.983663, -1.53, -3.198455),
  new THREE.Vector3(2.963501, -1.53, -3.598899),
  new THREE.Vector3(2.659824, -1.53, -3.836541),
  new THREE.Vector3(2.191105, -1.53, -4.028251),
  new THREE.Vector3(2.240390, -1.53, -4.310693),
  new THREE.Vector3(2.572504, -1.53, -4.661991),
  new THREE.Vector3(2.307537, -1.53, -3.886087),
  new THREE.Vector3(3.027315, -1.53, -2.798007),
  new THREE.Vector3(3.300683, -1.53, -1.784693),
  new THREE.Vector3(3.619300, -1.53, -0.607687),
  new THREE.Vector3(4.113084, -1.53, -0.698215),
  new THREE.Vector3(4.958174, -1.53, -0.692387),
  new THREE.Vector3(5.310518, -1.53, -0.713442),
  new THREE.Vector3(5.592218, -1.53, -0.711499),
  new THREE.Vector3(5.923636, -1.53, -0.991075),
  new THREE.Vector3(6.302977, -1.53, -1.168141),
  new THREE.Vector3(6.393383, -1.53, -1.641758),
];

// 就是把点转换成路径曲线
let t = 0;                 // 曲线上位置参数
const speed = 0.0005;       // 人物移动速度

// 保留之前的环境贴图加载方法
const loadEnvironment = async () => {
  // 初始化加载器
  const loader = new THREE.TextureLoader();
  // 加载照片
  const texture = await loader.loadAsync('360.jpg');
  //承载立方体贴图的六个面
  const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(texture.image.height);
  //将加载好的 equirectangular(等距矩形)纹理 转换为 立方体贴图(CubeMap)
  cubeRenderTarget.fromEquirectangularTexture(renderer, texture);
  //设置立方体贴图为整个场景的 背景贴图
  scene.background = cubeRenderTarget.texture;
  //设置立方体贴图为 物体的环境贴图(Environment Map),用于真实的反射和光照计算
  scene.environment = cubeRenderTarget.texture;
};
let floor; // 全局声明地板对象
// 初始化场景
const initScene = () => {
  //场景
  scene = new THREE.Scene();
  //相机
  camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
  );
  camera.position.set(0, 1.5, 15);
  //开启抗锯齿,允许使用透明背景
  renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
  //设置渲染器的输出画布尺寸为当前窗口大小:window.innerWidth:浏览器可视区域的宽度window.innerHeight:浏览器可视区域的高度
  renderer.setSize(window.innerWidth, window.innerHeight);
  //将渲染器的输出画布添加到页面中,让其显示出来就是ref="threeCanvas"对应的元素中
  threeCanvas.value.appendChild(renderer.domElement);

  //轨道控制器允许鼠标控制 摄像机轨道运动
  controls = new OrbitControls(camera, renderer.domElement);
  //设置摄像机与目标点(默认是 [0, 0, 0])之间的最小距离。
  controls.minDistance = 1;
  //设置摄像机与目标点的最大距离,限制用户不能缩放太远。
  controls.maxDistance = 500;
  //设置为 Math.PI,表示用户可以把摄像机从最上面旋转到最下面,没有垂直方向的限制
  controls.maxPolarAngle = Math.PI;

  const lights = [
    new THREE.AmbientLight(0xffffff, 0.6),
    new THREE.DirectionalLight(0xffffff, 0.8),
    new THREE.DirectionalLight(0xffffff, 0.4),
    new THREE.PointLight(0xffffff, 0.5)
  ];
  lights[1].position.set(5, 10, 7);
  lights[2].position.set(-5, 5, -5);
  lights[3].position.set(0, 3, 3);
  //将上述所有灯光统一添加到 scene 场景中,才能在渲染时生效。
  lights.forEach(light => scene.add(light));
  // 创建一个大地板
  const floorGeometry = new THREE.PlaneGeometry(20, 20);
  const floorMaterial = new THREE.MeshStandardMaterial({
    color: 0x999999,
    transparent: true,
    opacity: 0,
    side: THREE.DoubleSide,
  });
  floor = new THREE.Mesh(floorGeometry, floorMaterial);
  floor.rotation.x = -Math.PI / 2; // 地板水平放置
  floor.position.y = -1.53;        // 对齐你的点击点 y 坐标
  floor.name = 'Floor';
  floor.receiveShadow = true;
  scene.add(floor);

  // **调用环境贴图加载**
  loadEnvironment();
};

// 加载场景模型
const loadModel = () => {
  const loader = new GLTFLoader();
  loader.setPath('/models1/');
  loader.load('scene.gltf', (gltf) => {
    model = gltf.scene;

    // 计算包围盒
    const box = new THREE.Box3().setFromObject(model);
    console.log("box", box);

    const center = new THREE.Vector3();
    box.getCenter(center);


    // 移动模型到原点
    model.position.x -= center.x;
    model.position.y -= center.y;
    model.position.z -= center.z;

    // 缩放模型
    const size = new THREE.Vector3();
    box.getSize(size);
    const maxAxis = Math.max(size.x, size.y, size.z);
    const scaleFactor = 10 / maxAxis;
    model.scale.setScalar(scaleFactor);

    scene.add(model);

    // 调整摄像机位置和轨道控制范围
    camera.position.set(0, size.y * scaleFactor * 1.5, size.z * scaleFactor * 2.5);
    controls.minDistance = maxAxis * scaleFactor * 0.5;
    controls.maxDistance = maxAxis * scaleFactor * 10;
  }, undefined, (error) => {
    console.error('加载场景模型失败:', error);
  });
};


// 加载老人模型并准备动画
const loadOldManModel = () => {
  const loader = new GLTFLoader();
  loader.setPath('/oldmodels/');
  loader.load('scene.gltf', (gltf) => {
    oldMan = gltf.scene;
    oldMan.scale.set(0.01, 0.01, 0.01);
    oldMan.name = 'OldMan';
    oldMan.position.set(0, 0, 0);
    scene.add(oldMan);

    if (gltf.animations && gltf.animations.length > 0) {
      //AnimationMixer 是 Three.js 专门用于管理动画播放的对象。它必须绑定到一个 Object3D(一般是模型的 scene)上。这个 mixer 后续用于播放、暂停、切换等动画控制。注意:一个模型对应一个 AnimationMixer,多个模型需创建多个 mixer。
      mixer = new THREE.AnimationMixer(oldMan);
      //从动画列表中查找名称为 'Take 001' 的动画剪辑如果没找到 'Take 001',就使用第一个动画(gltf.animations[0])作为备选

      const shuffleClip = THREE.AnimationClip.findByName(gltf.animations, 'Take 001') || gltf.animations[0];
      //mixer 中获取这个动画剪辑对应的 动画动作对象(Action
      const action = mixer.clipAction(shuffleClip);
      action.play();
    }
  }, undefined, (error) => {
    console.error('加载老人模型失败:', error);
  });
};

// 鼠标点击拾取模型表面路径点
function onMouseClick(event) {
  const rect = renderer.domElement.getBoundingClientRect();
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
  mouse.y = - ((event.clientY - rect.top) / rect.height) * 2 + 1;

  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObject(floor); // 只检测地板

  if (intersects.length > 0) {
    const point = intersects[0].point.clone();
    console.log('地板点击位置:', point);
    pathPoints.push(point);

    const sphere = new THREE.Mesh(
      new THREE.SphereGeometry(0.05, 12, 12),
      new THREE.MeshBasicMaterial({ color: 0xff0000 })
    );
    sphere.position.copy(point);
    scene.add(sphere);

    updateCurve();
    t = 0;
  }
}


// 更新曲线及其可视化
function updateCurve() {
  if (pathPoints.length < 2) {
    curve = null;
    return;
  }
  curve = new CatmullRomCurve3(pathPoints, false); // false表示路径不闭合

  // 移除旧路径线
  const oldLine = scene.getObjectByName('pathLine');
  if (oldLine) {
    scene.remove(oldLine);
    oldLine.geometry.dispose();
    oldLine.material.dispose();
  }

  const points = curve.getPoints(50);
  const geometry = new THREE.BufferGeometry().setFromPoints(points);
  const material = new THREE.LineBasicMaterial({ color: 0x00ff00 ,transparent: true,
  opacity: 0 }); 
  const pathLine = new THREE.Line(geometry, material);
  pathLine.name = 'pathLine';
  scene.add(pathLine);
}

// 动画循环
const animate = (time) => {
  time *= 0.001;
  requestAnimationFrame(animate);

  const delta = clock.getDelta();
  if (mixer) mixer.update(delta);
  tweenGroup.update(time * 1000);
  controls.update();

  // 沿曲线移动老人模型
  if (curve && oldMan) {
    t += speed;
    if (t > 1) t = 0;

    const pos = curve.getPointAt(t);
    const nextPos = curve.getPointAt((t + 0.01) % 1);
//把机器人放到“路径上 t 这个位置”的点。


    oldMan.position.copy(pos);
    oldMan.lookAt(nextPos);
  }

  renderer.render(scene, camera);
};

// 监听窗口尺寸变化
const handleResize = () => {
  const width = window.innerWidth;
  const height = window.innerHeight;
  renderer.setSize(width, height);
  camera.aspect = width / height;
  camera.updateProjectionMatrix();
};

// 生命周期钩子
onMounted(() => {
  initScene();
  loadModel();
  loadOldManModel();
  animate();
  updateCurve(); // 初始化路径

  window.addEventListener('resize', handleResize);
  // window.addEventListener('click', onMouseClick);
});

onBeforeUnmount(() => {
  window.removeEventListener('resize', handleResize);
  // window.removeEventListener('click', onMouseClick);`  

  renderer.dispose();
  controls.dispose();
});
</script>

<style scoped>
#c {
  width: 100%;
  height: 100%;
  display: block;
}

body {
  margin: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
}
</style>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值