添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
THREEJS:由鼠标控制的空间物体的运动

THREEJS:由鼠标控制的空间物体的运动

在threejs中,物体的转动并不象人们想象中的方式去进行.用一个汽车来举例.当一辆汽车转弯后,应该是按照转弯后的方向去前进,可是在threejs中,汽车转弯后,是侧着身子沿着原来的方向在运动.因为在threejs中,物体的运动都是以其所在的坐标系为参照的.物体的运动并不影响其所在的坐标系的姿态.所以汽车的运动并不能被真实地反映出来.

汽车的运动只是个平面运动,相对于汽车来说,在三维空间的飞机的运动就更复杂一些.同样,在三维空间中,也存在类似的情况.

这种情况,并不是简单地改变物体所在的坐标系就能解决问题的.因为物体的旋转会造成其在世界坐标系下的位置发生变化,而这种变化会因为物体所在的坐标系的变换造成累加.解决的办法就是,在移动物体的父坐标系时, 按照单位长度投影分量进行移动,变换后,坐标系内的物体整体进行移动.而旋转则不受影响.

// 旋转物体的父坐标系 (180,表示每次旋转1度)
function Rotating(xdir,ydir,zdir) {
localSystem1.rotation.x += xdir * Math.PI/180;
localSystem1.rotation.y += ydir * Math.PI/180;
localSystem1.rotation.z += zdir * Math.PI/180;
// console.log('Rotating:',localSystem1.rotation)
}

// 移动物体的父坐标系, 按照单位长度投影分量进行移动
// 变换后,坐标系内的物体整体进行移动(0.1表示每次移动的长度)
function Moving(xdir,ydir,zdir) {
let sPoint = new THREE.Vector3(0.1*xdir,0.1*ydir,0.1*zdir);
let nPoint = LocalToWorld(localSystem1,sPoint);
localSystem1.position.x = nPoint.x;
localSystem1.position.y = nPoint.y;
localSystem1.position.z = nPoint.z;
// console.log('Moving:',localSystem1.position)
}

以下是对本文所附程序的简单说明:

1,建立一个子坐标系localSystem1,物体(mesh)即建立在这个坐标系中(表现为一个三角形);

2,空间的各种运动形式均通过按键来模拟.具体如下:

位移:

X方向 q左移/右移7 [5,-5,0]

Y方向 w上升/下降8 [3,-3,0]

Z方向 e前进/后退9 [5,-5,1]

旋转:

X方向 a上翻/下翻4 [0.3,-0.3,0]

Y方向 s左转/右转5 [0.5,-0.5,0]

Z方向 d左滚/右滚6 [-0.3,0.3,0]

加速: 按正向键为加速,直到极限值

减速: 按反向键为减速,直到极限值

SPACEBAR: 开始/暂停

3,在交互菜单里选择"orbit",可以显示端点的运动轨迹.这对于理解点的空间运动很有帮助.

4,程序中假定Z轴正向为前进方向,且符合右手定则.所以有以下的对应关系:

俯仰角:X轴

偏航角:Y轴

翻滚角:Z轴

以下为原程序:

<template>
 <div id="info1"><pre>
   X  q左移/7右移 
   Y  w上升/8下降 
   Z  e前进/9后退 
   X: a上翻/4下翻 
   Y: s左转/5右转 
   Z: d左滚/6右滚 
</pre></div> 
</template>
<script type="module">
 // 跟踪点在子坐标系中的移动轨迹
 // 将点设置为实体Points对象,随着子坐标系的变换,点的坐标随之变换,再转换到世界坐标系中,从而描绘出轨迹
 // 不论是物体移动还是坐标系移动,都可以绘出物体移动的轨迹 (以上假定Z轴正向为前进方向.)
//              Y                 位移:                                                      
//              ^                    X           q左移/右移7                                             
//              |    ^ Z             Y           w上升/下降8                                      
//              |   /                Z           e前进/后退9                         
//              |  /              旋转:                                                                                         
//              | /                  X:          a上翻/下翻4                      
//   X <--------|------------        Y:          s左转/右转5                                 
//             /|                    Z:          d左滚/右滚6                       
//            / |                  加速: 按正向键为加速,直到极限值                                                     
//           /  |                  减速: 按反向键为减速,直到极限值                                                         
//          /   |                  SPACEBAR: 开始/暂停
 // Gu Laicheng , 2022-04-26
import * as THREE from 'three';
import Stats from 'three/examples/jsm/libs/stats.module.js';
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'; 
// 导入控制器,轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; 
import 'default-passive-events'
let camera, scene, renderer, stats; 
var isMouseDown = false; 
let mesh, material, geometry, texture;
let axesHelper0,axesHelper1 ;
let localSystem1 ;
let gridHelper, orbitControls;
var time = 0;  
var xdir=false, ydir=false, zdir=false;
var I=0;  
var director=1;
var LineIDs=[]; 
let point1 , point2;
let WorldDirection= new THREE.Vector3();
const controls = new function () {
 this.showAxes = true;
 this.showGrid = false; 
 this.orbit = false;
initScene();
draw();
initGui(); 
animate();
function initScene() {
  camera = new THREE.PerspectiveCamera( 27, window.innerWidth / window.innerHeight, 1, 3500 );
  camera.position.x = 125;
  camera.position.y = 55;
  camera.position.z = 350;
  scene = new THREE.Scene();
  scene.background = new THREE.Color( 0x050505 );
  scene.autoUpdate = true; // 默认值为true,若设置了这个值,则渲染器会检查每一帧是否需要更新场景及其中物体的矩阵。 当设为false时,你必须亲自手动维护场景中的矩阵。
 const light = new THREE.HemisphereLight();
  scene.add( light );
 //  定义多个坐标系统:
    axesHelper0 = new THREE.AxesHelper(35);
    axesHelper1 = new THREE.AxesHelper(25); 
  scene.add( axesHelper0 )
  localSystem1 = new THREE.Object3D()
  scene.add(localSystem1)
  localSystem1.add(axesHelper1)
  localSystem1.position.set(0,10,0);
  localSystem1.rotation.x = Math.PI/3; 
// 下以更新世界坐标的动作在render时自动执行,在render之前需要手工执行一次.
scene.updateMatrixWorld(); // 这个动作是自动嵌套(递归)执行的 ???
//localSystem1.updateMatrixWorld(); 
  gridHelper = new THREE.GridHelper(100, 100, 0x5C5C5C, 0x888888 );
 // scene.add(gridHelper);
  renderer = new THREE.WebGLRenderer( { antialias: true } );
  renderer.setPixelRatio( window.devicePixelRatio );
  renderer.setSize( window.innerWidth, window.innerHeight );
  document.body.appendChild( renderer.domElement );
  orbitControls = new OrbitControls(camera, renderer.domElement);
 //设置控制器的中心点
 //orbitControls.target.set( 0, 5, 0 );
 // 如果使用animate方法时,将此函数删除
 //orbitControls.addEventListener( 'change', render );
 // 使动画循环使用时阻尼或自转 意思是否有惯性
      orbitControls.enableDamping = true;
 //动态阻尼系数 就是鼠标拖拽旋转灵敏度
 //orbitControls.dampingFactor = 0.25;
 //是否可以缩放
      orbitControls.enableZoom = true;
 //是否自动旋转
      orbitControls.autoRotate = true;
      orbitControls.autoRotateSpeed = 0.5;
 //设置相机距离原点的最远距离
      orbitControls.minDistance = 1;
 //设置相机距离原点的最远距离
      orbitControls.maxDistance = 2000;
 //是否开启右键拖拽
      orbitControls.enablePan = true;
  stats = new Stats();
  document.body.appendChild( stats.dom );
  geometry = new THREE.BufferGeometry();
  window.addEventListener( 'resize', onWindowResize );
  window.addEventListener( 'keydown', function(event) { //KeyBoardEvent
    console.log("key:",event.key,',code:',event.code)
 if ( event.key === ' ') {// SPACEBAR
      isMouseDown = !isMouseDown; 
    } else if ( event.key === 'a') { Rotating(1,0,0); 
    } else if ( event.key === 's') { Rotating(0,1,0); 
    } else if ( event.key === 'd') { Rotating(0,0,1); 
    } else if ( event.key === '4') { Rotating(-1,0,0); 
    } else if ( event.key === '5') { Rotating(0,-1,0); 
    } else if ( event.key === '6') { Rotating(0,0,-1); 
    } else if ( event.key === 'q') { Moving(1,0,0);
    } else if ( event.key === 'w') { Moving(0,1,0);
    } else if ( event.key === 'e') { Moving(0,0,1);
    } else if ( event.key === '7') { Moving(-1,0,0);
    } else if ( event.key === '8') { Moving(0,-1,0);
    } else if ( event.key === '9') { Moving(0,0,-1);
    } else if ( event.key === 'v') { 
      console.log('q1:',mesh.quaternion, mesh.rotation)
      rotateEndPoint = new THREE.Vector3(-1, 0, 0); // projectOnTrackball(0.1, 0.1);
      curQuaternion = mesh.quaternion; 
 const quaternion = new THREE.Quaternion();
      quaternion.setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), Math.PI / 12 );
      quaternion.setFromAxisAngle( new THREE.Vector3( 1, 0, 0 ), Math.PI / 2 );
      mesh.applyQuaternion( quaternion );
      console.log('q2:',mesh.quaternion, mesh.rotation)
    } else if(event.key == 'ArrowLeft') {
      director = 1;
    } else if(event.key == 'ArrowUp') {
      director = 1;
    } else if(event.key == 'ArrowRight') {
      director = -1;
    } else if(event.key == 'ArrowDown') {
      director = -1;
    } else if(event.key == '/') {
      mesh.getWorldDirection(WorldDirection);
      console.log('position :',mesh.position);
      console.log('rotation :',mesh.rotation);
      console.log('scale  :',mesh.scale ); 
      console.log('WorldDirection  :',WorldDirection); 
      console.log('dir  :',xdir,ydir,zdir); 
 // 键盘上下左右 方向键的键码(keyCode)是38、40、37和39
 // key: ArrowUp ,code: ArrowUp
 // key: ArrowDown ,code: ArrowDown
 // key: ArrowLeft ,code: ArrowLeft
 // key: ArrowRight ,code: ArrowRight
 // window.addEventListener('mouseup', onMouseUp);
// 动态空间曲线
function draw(){ 
  point1 = new THREE.Points();
  point1.position.set(-10.0, -10.0,  10.0);
  point2 = new THREE.Points();
  point2.position.set( 10.0, -10.0,  10.0);
  localSystem1.add(point1); 
  localSystem1.add(point2); 
  triangle(localSystem1);
// triangle
function triangle(s) { 
  geometry = new THREE.BufferGeometry(); 
 const vertices = new Float32Array( [
    -10.0, -10.0,  10.0,
 10.0, -10.0,  10.0,
 10.0,  10.0,  10.0,
 //  1.0,  1.0,  1.0,
 // -1.0,  1.0,  1.0,
 // -1.0, -1.0,  1.0
 var colors = [
 1,0,0,
 0,1,0,
 0,0,1,
// itemSize = 3 因为每个顶点都是一个三元组。
  geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
  geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
  material = new THREE.MeshBasicMaterial( { 
 // color: 0xff0000,
    wireframe: true,
    vertexColors: true } );
  mesh = new THREE.Mesh( geometry, material );
  s.add(mesh); 
// line
function lines(s,points,c) {
 const material = new THREE.LineBasicMaterial({
      color: c
 const geometry = new THREE.BufferGeometry().setFromPoints( points );
 const mesh = new THREE.Line( geometry, material );
    s.add( mesh );
 return mesh.id;
function initGui() {
 const gui = new GUI();
  gui.add(controls, 'showAxes').onChange(()=>{
 if (controls.showAxes) {
      scene.add( axesHelper0 );
      localSystem1.add( axesHelper1 ) ;
    } else {
      scene.remove( axesHelper0 )
      localSystem1.remove( axesHelper1 ) ;
  gui.add(controls, 'showGrid').onChange(()=>{
 if (controls.showGrid) {
      scene.add( gridHelper );
    } else {
      scene.remove( gridHelper );
  gui.add(controls,"orbit");
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize( window.innerWidth, window.innerHeight );
function animate() {
 if (controls.showAxes) {
    scene.add( axesHelper0 );
    localSystem1.add( axesHelper1 ) ;
  } else {
    scene.remove( axesHelper0 )
    localSystem1.remove( axesHelper1 ) ;
 if (controls.showGrid) {
    scene.add( gridHelper );
  } else {
    scene.remove( gridHelper );
  requestAnimationFrame( animate );
  render();
  stats.update();
 var disp=true; 
 let LineID1,LineID2;
 let wpoints1 = [] ,wpoints2 = [];
 let geometry1,geometry2,colors = [];
function render() {
 if (isMouseDown) { 
 if( time > 200 && time < 41000 && time%2 === 0 ) { 
 //   rubber();
 // if(controls.orbit) {
 //   orbit();
 // } else {
 //   wpoints1 = [];
 //   wpoints2 = [];
// ====================================================================
 if(disp) {
 // console.log('position :',wp1);
 // console.log('rotation :',mesh.rotation);
 // console.log('scale  :',mesh.scale );
      disp=false;
    time++;
  renderer.render(scene, camera);
// 旋转物体的父坐标系
function Rotating(xdir,ydir,zdir) {
  localSystem1.rotation.x +=  xdir * Math.PI/18; 
  localSystem1.rotation.y +=  ydir * Math.PI/18; 
  localSystem1.rotation.z +=  zdir * Math.PI/18; 
  rubber();
 if(controls.orbit) {
    orbit();
  } else {
    wpoints1 = [];
    wpoints2 = [];
// 移动物体的父坐标系, 按照单位长度投影分量进行移动
// 变换后,坐标系内的物体整体进行移动
function Moving(xdir,ydir,zdir) {
 let sPoint = new THREE.Vector3(0.5*xdir,0.5*ydir,0.5*zdir);
 let nPoint = LocalToWorld(localSystem1,sPoint);
  localSystem1.position.x = nPoint.x;  
  localSystem1.position.y = nPoint.y;  
  localSystem1.position.z = nPoint.z;  
  rubber();
 if(controls.orbit) {
    orbit();
  } else {
    wpoints1 = [];
    wpoints2 = [];
function rubber() {
  LineIDs.forEach((item)=>{purgeObj(item)});
  LineIDs = [
    lines(scene,[
      LocalToWorld ( localSystem1, point1.position ),
 new THREE.Vector3(0,0,0),
      LocalToWorld ( localSystem1, point2.position ),
    ],0xFF00FF)
// ========  绘制点的轨迹曲线  =========================================
function orbit() { 
  purgeObj(LineID1);
  purgeObj(LineID2); 
 let wp1 = LocalToWorld (localSystem1, point1.position );
 let wp2 = LocalToWorld (localSystem1, point2.position );
  wpoints1.push(wp1);
  wpoints2.push(wp2);
  geometry1 = new THREE.BufferGeometry().setFromPoints( wpoints1 );
  geometry2 = new THREE.BufferGeometry().setFromPoints( wpoints2 );
  colors.push(0.6,0.6,0.6);
  geometry1.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
  geometry2.setAttribute( 'color', new THREE.Float32BufferAttribute( colors, 3 ) );
 const material = new THREE.LineBasicMaterial({
 // color: 0xFFFF00,
    vertexColors: true
 const mesh1 = new THREE.Line( geometry1, material );
  scene.add(mesh1);
  LineID1 = mesh1.id;
 const mesh2 = new THREE.Line( geometry2, material );
  scene.add(mesh2);
  LineID2 = mesh2.id; 
 // console.log('orbit Line :',wpoints1, wpoints2)
// 为了避免 localToWorld 方法的怪异行为
// s: scene
// p: point
function LocalToWorld(s,p) {
 let p0 = p.clone();
 return s.localToWorld ( p0 );
function purgeObj(objid) {
 if(!objid) return;
 let obj = scene.getObjectById ( objid );
 if(!obj) return;
 const mater = obj.material;
 const geome = obj.geometry;
  scene.remove(obj); 
  mater.dispose();
  geome.dispose();
</script>
<style scoped>
body {
 margin: 0;
 background-color: #000;
 color: #fff;
 font-family: Monospace;
 font-size: 13px;
 line-height: 24px;
 overscroll-behavior: none;
canvas {
 width: 100%;
 height: 100%;
 display: block;
#widget select option {
 background-color: #333333;
#info1 {
 position: absolute;
 background-color: rgba(0,0,0,0.2);
 top: 0px;
 left: 0px;
 width: 40%;
 height: 120px;
 padding: 10px;
 color: rgb(223, 209, 19);
 box-sizing: border-box;
 text-align: center;
 -moz-user-select: none;
 -webkit-user-select: none;
 -ms-user-select: none;
 user-select: none;
 pointer-events: none;
 /* opacity:0.2; */
 z-index: 1; /* TODO Solve this in HTML */
#overlay {
 position: absolute;
 font-size: 16px;
 z-index: 2;
 top: 0;
 left: 0;
 width: 100%;
 height: 100%;
 display: flex;
 align-items: center;