ThreeJS-3D Tutorial Three: Panning and Zooming + Object Movement Along the Trajectory

We will have some such needs in the project. When we visualize a scene, we need to look down, pan, and zoom to facilitate the observation of data or models in the scene. The reason for taking this case 1. This is a very practical requirement. I
believe Many people will use
2. I think that in actual cases we can learn relevant knowledge points to make it easier to absorb.
In order to enrich the knowledge points of this article, I also added a scene in which objects move along a trajectory. The following code will introduce it. Return In the previous question three, you can use the settings of OrbitControls to easily implement related scenarios. The code is as follows:

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;

It should be noted that mobile terminals and PCs have different configurations. MinDistance and maxDistance can be set according to actual needs. At this step, it is just the operation of controls. It is already capable of achieving the effect, but the setting of the camera is also indispensable, that is, The camera must be looking down

First, let’s take a look at the overall renderings and
Insert image description here
the code:

<!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>

Here is all the code. It can be seen that three uses version 155. You can download it from the official website and change the path locally. You can change the image URL to a local image, and the project will start running.

The middle cube is rotating around its own y-axis, and the outer cube moves along the trajectory of the curve by obtaining the coordinate points on the curve.

Here are some new knowledge points:

new THREE.TextureLoader().load('.../materials/img/view3.jpg');
TextureLoader is three's method of loading images. We can also use
let loader = new THREE.TextureLoader();
loader.load(' url', (img) => { // img This img is the image after loading });

At this time, there is a question, synchronous or asynchronous. Let's look at the source code and it
Insert image description here
is actually asynchronous. After getting the picture, we found that the material has a map parameter, which can receive the picture as the material, so that the material can be very rich. Side refers to Both sides of the object, THREE.DoubleSide, means both sides must be rendered. If we only look at the outside, do not set this here, which can also save some performance.

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

The last knowledge point is that the object moves along the trajectory. We change this line of code
curveObject.visible = false; to true and everyone can see the trajectory.
The logic code has been written in great detail. You can also think about what other methods can be used as curve.
If you find it useful, please give it a thumbs up! ! !

Guess you like

Origin blog.csdn.net/weixin_44384273/article/details/133301137