ThreeJS-3D教学三:平移缩放+物体沿轨迹运动

news2024/12/30 2:33:58

我们在项目中会有一些这样的需求,我们可视化一个场景,需要俯视、平移、缩放,方便观察场景中的数据或者模型,之所以把这个案例拿出来
1、这是个很实用的需求,我相信很多人会用到
2、我自己认为在实际案例中我们可以学习相关知识点更易吸收些
为了丰富本篇文章知识点,我还加入了一个物体沿轨迹运动的场景,下面代码会介绍到,回到之前的问题three中可以利用对 OrbitControls 的设置很轻松的实现相关场景,代码如下:

controls = new OrbitControls( camera, renderer.domElement );
  // 移动端控制平移 缩放
  // controls.touches = {
  //   ONE: THREE.TOUCH.PAN,
  //   TWO: THREE.TOUCH.DOLLY_PAN
  // };
  // PC 左平移,右旋转 滚轮放大缩小   默认是右平移
  controls.mouseButtons = {
    LEFT: THREE.MOUSE.PAN,
    MIDDLE: THREE.MOUSE.DOLLY,
    RIGHT: THREE.MOUSE.ROTATE
  };
  // 设置最大最小视距
  controls.minDistance = 20;
  controls.maxDistance = 1000;
  controls.autoRotate = false;
  controls.enableRotate = false;
  controls.enablePan = true;

需要注意的是移动端和PC是不同的配置,minDistance和maxDistance按实际需求设置即可,到这一步只是对controls的操作,已经有能力实现效果了,但是camera的设置也是不可或缺的,即camera必须是俯视的

先上下整体效果图
在这里插入图片描述
看下代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    body {
      width: 100%;
      height: 100%;
    }
    * {
      margin: 0;
      padding: 0;
    }
    .label {
      font-size: 20px;
      color: #000;
      font-weight: 700;
    }
  </style>
</head>
<body>
<div id="container"></div>
<script type="importmap">
  {
    "imports": {
      "three": "../three-155/build/three.module.js",
      "three/addons/": "../three-155/examples/jsm/"
    }
  }
</script>
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { CSS2DRenderer, CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
let stats, labelRenderer, curve, movingObjects;
let camera, scene, renderer, mesBox, target, controls;
const group = new THREE.Group();
let progress = 0; // 物体运动时在运动路径的初始位置,范围0~1
const velocity = 0.0005; // 影响运动速率的一个值,范围0~1,需要和渲染频率结合计算才能得到真正的速率
let widthImg = 200;
let heightImg = 200;
init();
initHelp();
initLight();
axesHelperWord();
animate();
// 添加一个物体
mesBox = addGeometries();
// 添加平面
addPlane();
// 添加路径
makeCurve();
// 添加一个运动的物体
movingObjects = addMovingObjects();


window.addEventListener('mousemove', mousemoveFun);

function addPlane() {
  // 创建一个平面 PlaneGeometry(width, height, widthSegments, heightSegments)
  const planeGeometry = new THREE.PlaneGeometry(widthImg, heightImg, 1, 1);
  // 创建 Lambert 材质:会对场景中的光源作出反应,但表现为暗淡,而不光亮。
  const planeMaterial = new THREE.MeshLambertMaterial({
    color: 0xffffff
  });
  const plane = new THREE.Mesh(planeGeometry, planeMaterial);
  // 以自身中心为旋转轴,绕 x 轴顺时针旋转 45 度
  plane.rotation.x = -0.5 * Math.PI;
  plane.position.set(0, -4, 0);
  scene.add(plane);
}

function addGeometries() {
  let img = new THREE.TextureLoader().load('../materials/img/view4.jpg');
  img.repeat.x = img.repeat.y = 5;
  const geometry = new THREE.BoxGeometry( 10, 10, 10 );
  let material = new THREE.MeshPhongMaterial({
    map: img,
    flatShading: true,
    side: THREE.DoubleSide,
    transparent: 1
  });

  const mesh = new THREE.Mesh( geometry, material );
  mesh.position.x = 0;
  mesh.position.y = 0;
  mesh.position.z = -20;
  scene.add( mesh );
  return mesh;
}

function addMovingObjects() {
  let img = new THREE.TextureLoader().load('../materials/img/view3.jpg');
  const geometry = new THREE.BoxGeometry( 10, 10, 10 );
  const material = new THREE.MeshPhongMaterial({
    map: img,
    flatShading: true,
    side: THREE.DoubleSide,
    transparent: 1
  });
  const mesh = new THREE.Mesh( geometry, material );
  scene.add( mesh );
  return mesh;
}

function makeCurve() {
  // 创建一个环形路径
  curve = new THREE.CatmullRomCurve3([
    new THREE.Vector3(80, 0, 80),
    new THREE.Vector3(80, 0, -80),
    new THREE.Vector3(-80, 0, -80),
    new THREE.Vector3(-80, 0, 80)
  ]);
  curve.curveType = "catmullrom";
  curve.closed = true;//设置是否闭环
  curve.tension = 0; //设置线的张力,0为无弧度折线

  // 为曲线添加材质在场景中显示出来,不显示也不会影响运动轨迹,相当于一个Helper
  const points = curve.getPoints(20);
  const geometry = new THREE.BufferGeometry().setFromPoints(points);
  const material = new THREE.LineBasicMaterial({
    color: '#f00',
    linewidth: 1
  });

  // Create the final object to add to the scene
  const curveObject = new THREE.Line(geometry, material);
  curveObject.position.y = 1;
  curveObject.visible = false;
  scene.add(curveObject);
}

function mousemoveFun() {
  let width = widthImg;
  let height = heightImg;
  let newPo;
  let newPoHeight;

  const prevPos = camera.position.clone();

  if (camera.position['x'] > width / 2) {
    newPo = width / 2 - 0.01;
    camera.position.set(newPo, prevPos.y, prevPos.z);
    controls.target = new THREE.Vector3(newPo, 0, prevPos.z);
    controls.enablePan = false;
  } else if (camera.position['x'] < -width / 2) {
    newPo = -width / 2 + 0.01;
    camera.position.set(newPo, prevPos.y, prevPos.z);
    controls.target = new THREE.Vector3(newPo, 0, prevPos.z);
    controls.enablePan = false;
  } else if (camera.position['z'] > height / 2) {
    // 因为我们的坐标系 y轴在上 所以平移时 height 即是 z
    newPoHeight = height / 2 - 0.01;
    camera.position.set(prevPos.x, prevPos.y, newPoHeight);
    controls.target = new THREE.Vector3(prevPos.x, 0, newPoHeight);
    controls.enablePan = false;
  } else if (camera.position['z'] < -height / 2) {
    newPoHeight = -height / 2 + 0.01;
    camera.position.set(prevPos.x, prevPos.y, newPoHeight);
    controls.target = new THREE.Vector3(prevPos.x, 0, newPoHeight);
    controls.enablePan = false;
  } else {
    controls.enablePan = true;
  }
}

function init() {

  camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 10, 2000 );
  camera.up.set(0, 1, 0);
  camera.position.set(0, 100, 0);
  camera.lookAt(0, 0, 0);

  scene = new THREE.Scene();
  // scene.background = new THREE.Color( '#ccc' );

  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );

  labelRenderer = new CSS2DRenderer();
  labelRenderer.setSize( window.innerWidth, window.innerHeight );
  labelRenderer.domElement.style.position = 'absolute';
  labelRenderer.domElement.style.top = '0px';
  labelRenderer.domElement.style.pointerEvents = 'none';
  document.getElementById( 'container' ).appendChild( labelRenderer.domElement );

  controls = new OrbitControls( camera, renderer.domElement );
  // 移动端控制平移 缩放
  // controls.touches = {
  //   ONE: THREE.TOUCH.PAN,
  //   TWO: THREE.TOUCH.DOLLY_PAN
  // };
  // PC 左平移,右旋转 滚轮放大缩小   默认是右平移
  controls.mouseButtons = {
    LEFT: THREE.MOUSE.PAN,
    MIDDLE: THREE.MOUSE.DOLLY,
    RIGHT: THREE.MOUSE.ROTATE
  };
  // 设置最大最小视距
  controls.minDistance = 20;
  controls.maxDistance = 1000;
  controls.autoRotate = false;
  controls.enableRotate = false;
  controls.enablePan = true;

  window.addEventListener( 'resize', onWindowResize );

  stats = new Stats();
  stats.setMode(1); // 0: fps, 1: ms
  document.body.appendChild( stats.dom );

  scene.add( group );
}

function initLight() {
  let spotLight;
  spotLight = new THREE.SpotLight( 0xffffff, 500 );
  spotLight.name = 'Spot Light';
  spotLight.angle = Math.PI / 5;
  spotLight.penumbra = 0.1;
  spotLight.position.set( 0, 5, 10 );
  spotLight.castShadow = true;
  spotLight.shadow.camera.near = 2;
  spotLight.shadow.camera.far = 100;
  spotLight.shadow.mapSize.width = 1024;
  spotLight.shadow.mapSize.height = 1024;
  scene.add( spotLight );

  const AmbientLight = new THREE.AmbientLight(0xCCCCCC, 2);
  scene.add( AmbientLight );
}

function initHelp() {
  // const size = 100;
  // const divisions = 5;
  // const gridHelper = new THREE.GridHelper( size, divisions );
  // scene.add( gridHelper );

  // The X axis is red. The Y axis is green. The Z axis is blue.
  const axesHelper = new THREE.AxesHelper( 100 );
  scene.add( axesHelper );
}

function axesHelperWord() {
  let xP = addWord('X轴');
  let yP = addWord('Y轴');
  let zP = addWord('Z轴');
  xP.position.set(50, 0, 0);
  yP.position.set(0, 50, 0);
  zP.position.set(0, 0, 50);
}

function addWord(word) {
  let name = `<span>${word}</span>`;
  let moonDiv = document.createElement( 'div' );
  moonDiv.className = 'label';
  // moonDiv.textContent = 'Moon';
  // moonDiv.style.marginTop = '-1em';
  moonDiv.innerHTML = name;
  const label = new CSS2DObject( moonDiv );
  group.add( label );
  return label;
}

function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
}

function animate() {
  requestAnimationFrame( animate );

  // mesh绕自身中心的 y 轴自转
  if (mesBox) {
    mesBox.rotation.y += 0.01;
  }

  if (curve && movingObjects) {
    if (progress <= 1 - velocity) {
      // 获取样条曲线指定点坐标
      const point = curve.getPointAt(progress);
      movingObjects.position.set(point.x, point.y, point.z);
      progress += velocity;
    } else {
      progress = 0;
    }
  }

  stats.update();
  controls.update();
  labelRenderer.render( scene, camera );
  renderer.render( scene, camera );
}
</script>
</body>
</html>

这里是全部代码,可以看出 three用的是 155版本,大家从官网上下载到本地改下路径即可。图片url你可以换成本地图片,项目就能跑起来了。

中间立方体在做了一个环绕自身y轴旋转,外面的立方体通过获取curve上的坐标点,沿着curve的轨迹运动。

这里有一些新的知识点:

new THREE.TextureLoader().load(‘…/materials/img/view3.jpg’);
TextureLoader是three加载图片的方法,我们还可以这样用
let loader = new THREE.TextureLoader();
loader.load(‘url’, (img) => {
// img 这个img就是加载完成后的图片
});

这时就一个疑问了,同步还是异步,我们来看源码
在这里插入图片描述
其实还是异步,拿到图片后我们发现material有一个map参数,可以接收图片作为材质,这样material就可以做的很丰富了,side是指物体的正反两面,THREE.DoubleSide即两面都要渲染,如果我们只看外面,不要设置这里,这样也能节省一些性能

 let material = new THREE.MeshPhongMaterial({
    map: img,
    flatShading: true,
    side: THREE.DoubleSide,
    transparent: 1
  });

最后一个知识点,即物体沿着轨迹运动,我们把这一行代码
curveObject.visible = false; 改为 true 大家就可以看到这个轨迹了。
逻辑代码中已经写的很详细了,大家也可以思考下还有哪些方法可以作为curve使用。
如果感觉有用,点个赞不过分吧!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1044885.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

高手必备!电脑剪辑视频的实用方法

随着数码时代的到来&#xff0c;越来越多的人开始使用电脑剪辑视频。电脑剪辑视频不仅可以为日常生活留下美好回忆&#xff0c;还可以为专业人士提供更多的创作可能性。在本文中&#xff0c;我们将介绍两种电脑剪辑视频的方法&#xff0c;不需要专业技能&#xff0c;只需要一台…

精细高效 智慧公厕为城市公共厕所联网管理装上“智慧中枢”

近年来&#xff0c;随着城市化进程的加速&#xff0c;城市公共设施的更新与改进也成为各地的重要任务之一。而其中&#xff0c;城市公共厕所的建设和管理更是备受关注。为了提高公厕的服务水平和城市形象&#xff0c;各地纷纷采取创新的智能化手段&#xff0c;将公厕与互联网技…

MT41K128M16JT DDR3寻址

MT41K128M16JT该DDR3共有27bit地址&#xff0c;分别如下&#xff1a; 因为DDR3控制器IP与DDR3的数据端口是128bit&#xff0c;而ddr3的物理宽度是16bit&#xff0c;所以ddr3写一次地址增加3。 ddr3控制器地址26–0&#xff0c;能访问到的最高地址是26-323。 ddr3控制器给出的地…

在线客服系统源码/在线对话聊天/多商户在线客服系统源码(可机器人自动聊天/支持app公众号网页H5)

源码介绍 在线客服系统源码/在线对话聊天/多商户在线客服系统源码&#xff0c;它是一款基于Web的在线客服系统&#xff0c;适用于实现企业与客户之间的在线沟通以及咨询服务。该系统支持多商户、无限座席&#xff0c;可以为多个企业提供在线客服服务&#xff0c;不受座席数量的…

跨境电商下半年如何布局,旺季销售策略全盘点,为火爆季节做准备

1.了解跨境电商市场趋势&#xff1a;在下半年旺季之前&#xff0c;了解目标市场的消费趋势、流行产品和购物偏好等因素非常重要。研究和分析市场数据&#xff0c;以及观察竞争对手的动向&#xff0c;可以帮助您更好地制定布局策略。 2.优化产品和供应链&#xff1a;确保您的产…

一文读懂云计算、云原生!

“上云”是一种大趋势。 根据Gartner的数据&#xff0c;未来云计算市场规模仍将保持20%以上的增长速度。到2025年&#xff0c;预计将有80%的企业会将其业务上云。 云的发展为何备受企业青睐&#xff1f;近年来火爆的云原生和云计算的区别到底是什么呢&#xff1f;为什么它如此…

工作流引擎笔记 20230927

现在需要一个管理数据业务流程配置功能。想到了workflow manage system/engine 概念调研 在工作流管理系统中&#xff0c;工作流引擎是核心组件&#xff0c;它负责驱动和执行定义好的工作流程。本文将详细介绍工作流引擎的关键特性、类型、与状态机的差异&#xff0c;以及设计…

Jmeter系列- 详解 CSV 数据文件设置

一、什么是csv文件 CSV文件&#xff1a;是指"逗号分隔值"&#xff08;Comma-Separated Values&#xff09;文件&#xff0c;它是一种简单的文件格式&#xff0c;用于存储表格数据&#xff0c;例如电子表格或数据库&#xff0c;可以用记事本和Excel打开&#xff0c;用…

《从零开始的Java世界》02面向对象(基础)

《从零开始的Java世界》系列主要讲解Javase部分&#xff0c;从最简单的程序设计到面向对象编程&#xff0c;再到异常处理、常用API的使用&#xff0c;最后到注解、反射&#xff0c;涵盖Java基础所需的所有知识点。学习者应该从学会如何使用&#xff0c;到知道其实现原理全方位式…

同为科技(TOWE)太阳能光伏直流电源浪涌保护器的产品应用

太阳能光伏发电是根据光生伏特效应原理&#xff0c;利用太阳电池将太阳光能直接转化为电能。实现方法是通过将太阳光转化为直流电&#xff0c;然后通过逆变器转换为交流电&#xff0c;以供家庭、企业和电网使用。太阳能光伏发电具有许多优点&#xff0c;例如环保、可再生、安全…

查看基站后台信息

查看基站后台信息 电脑配置固定ip: 192.168.1.99: 打开“网络和共享中心”&#xff0c;选择更改适配器设置&#xff1a; 右键“本地连接”&#xff0c;选择属性 基站网线直连电脑网口 Telnet 登录基站 打开dos窗口 windows键R”&#xff0c;输入cmd&#xff0c;点确定&…

TS中class类的基本使用

想要创建对象&#xff0c;必须要先定义类&#xff0c;所谓的类可以理解为对象的模型&#xff0c;程序中可以根据类创建所指定类型的对象。 一、使用class关键字定义类 class 类名 { } // 使用class关键字来定义一个类 class Person{}// 使用new关键字创建一个对象 const per …

新型基础设施:信息技术设施、融合基础设施和创新基础设施

新型基础设施主要包括信息技术设施、融合基础设施和创新基础设施三个方面。 信息基础设施包括哪些呢&#xff1f; 信息基础设施主要指基于新一代信息技术演化生成的基础设施。 1. 以5G、物联网、工业互联网、卫星互联网为代表的通信网络基础设施&#xff1b; 2. 以人工智能、…

大气环境一站式技能提升:SMOKE-CMAQ实践技术

查看原文>>>从小白到精通一站式技能提升&#xff1a;SMOKE-CMAQ实践技术 大气污染物排放是空气污染的源头&#xff0c;气象因素是影响污染程度的重要因素&#xff0c;因此空气质量模式要求气象资料和污染物排放清单作为输入&#xff0c;其中由于大气污染源复杂性、数…

Android ConstraintLayout app:layout_constraintHorizontal_weight

Android ConstraintLayout app:layout_constraintHorizontal_weight <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:…

中秋夜我读《乡村振兴战略下传统村落文化旅游设计》许少辉八月新书——地球红薯地中秋圆辉少许

中秋夜我读《乡村振兴战略下传统村落文化旅游设计》许少辉八月新书——地球红薯地中秋圆辉少许 中秋夜我读《乡村振兴战略下传统村落文化旅游设计》许少辉八月新书——地球红薯地中秋圆辉少许

视频剪辑中生成花字特效的代码案例详述

本文为阿里云智能媒体服务IMS「云端智能剪辑」实践指南第四期&#xff0c;通过运用官方提供的花字模版或自定义设计花字&#xff0c;完成丰富、精美的花字特效视频剪辑。 昱尘&#xff5c;作者 在视频后期剪辑中&#xff0c;“花字”是一种特别的存在。 通过使用适合视频的花…

北斗卫星导航系统:引领现代林业发展的先锋

北斗卫星导航系统&#xff1a;引领现代林业发展的先锋 随着人类社会的发展&#xff0c;林业作为生态环境保护和经济发展的重要组成部分&#xff0c;也在不断向前发展。为了更好地管理和保护森林资源&#xff0c;我们必须寻求一种新的方式来提高林业管理的效率。而北斗技术的应用…

vscode左键无法跳转到定义的文件

之前用vscode的时候&#xff0c;明明是可以ctrl键鼠标左键跳转到定义文件的&#xff0c;突然之间就不行了&#xff0c;鼠标移到引入上根本都没有下划线&#xff0c;无法跳转 解决方法&#xff1a; 项目的根目录新建 jsconfig.json 文件&#xff0c;代码如下 {"compiler…

Multisim14.0仿真(二十一)微分单稳态触发器

一、仿真原理图&#xff1a; 二、仿真效果图&#xff1a;