学习: Threejs (15)& Threejs (16)

切换动画不同动作(.weight)Threejs (15)

一、CSS2DRenderer(HTML标签)

引入扩展库CSS2DRenderer.js

model.js

javascript 复制代码
// 引入CSS2模型对象CSS2DObject
import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';

CSS2模型对象CSS2DObject

model.js

javascript 复制代码
const div = document.getElementById('tag');
// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);
javascript 复制代码
tag.position.set(50,0,50);

index.js

javascript 复制代码
scene.add(tag);

CSS2渲染器CSS2DRenderer

javascript 复制代码
// 引入CSS2渲染器CSS2DRenderer
import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js';

// 创建一个CSS2渲染器CSS2DRenderer
const css2Renderer = new CSS2DRenderer();

CSS2Renderer.render()渲染HTML标签

javascript 复制代码
// 用法和webgl渲染器渲染方法类似
css2Renderer.render(scene, camera);
// renderer.render(scene, camera);

CSS2Renderer.setSize()

javascript 复制代码
// width, height:canvas画布宽高度
css2Renderer.setSize(width, height);

渲染结果CSS2Renderer.domElement

javascript 复制代码
document.body.appendChild(css2Renderer.domElement);

CSS2Renderer.domElement重新定位

javascript 复制代码
css2Renderer.domElement.style.position = 'absolute';
css2Renderer.domElement.style.top = '0px';

二、HTML标签遮挡Canvas画布事件

.style.pointerEvents

javascript 复制代码
css2Renderer.domElement.style.pointerEvents = 'none';

CSS属性z-index

css2Renderer.domElement在下,threejs canvas画布在上,标签被canvas画布遮挡,看不到标签。

javascript 复制代码
renderer.domElement.style.zIndex = 1;
css2Renderer.domElement.style.zIndex = -1;

css2Renderer.domElement在上,threejs canvas画布在下,可以看到标签

javascript 复制代码
renderer.domElement.style.zIndex = -1;
css2Renderer.domElement.style.zIndex = 1;

三、 Canvas尺寸变化(HTML标签)

javascript 复制代码
// 画布跟随窗口变化
window.onresize = function () {
    const width = window.innerWidth;
    const height = window.innerHeight;
    // cnavas画布宽高度重新设置
    renderer.setSize(width,height);
    // 相机参数重新设置
    camera.aspect = width / height;
    camera.updateProjectionMatrix();
};
javascript 复制代码
//相机
const width = window.innerWidth;
const height = window.innerHeight;
// 画布跟随窗口变化
window.onresize = function () {
  renderer.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
};

Canvas全屏尺寸变化,CSS2渲染器设置

javascript 复制代码
  css2Renderer.setSize(window.innerWidth, window.innerHeight);

canvas局部布局,CSS2渲染器设置

码中web页面右下角div元素是用来插入canvas画布的HTML元素。

javascript 复制代码
  #tag {
            padding: 10px;
            color: #ffffff;
            background: rgba(25, 25, 25, 0.5);
            border-radius: 5px;
            width: 65px;
        }
javascript 复制代码
<div id="webgl" style="position: absolute;top: 60px;left: 200px">
 <div id="tag">
                标签内容
 </div>
</div>

CSS2渲染器输出的标签

javascript 复制代码
// 引入CSS2渲染器CSS2DRenderer
import { CSS2DRenderer } from "three/addons/renderers/CSS2DRenderer.js";
// 创建一个CSS2渲染器CSS2DRenderer
const css2Renderer = new CSS2DRenderer();
// width, height:canvas画布宽高度
css2Renderer.setSize(width, height);
document.getElementById("webgl").appendChild(css2Renderer.domElement);
css2Renderer.domElement.style.position = "absolute";
css2Renderer.domElement.style.top = "0px";
css2Renderer.domElement.style.pointerEvents = "none";
javascript 复制代码
// 渲染循环
function render() {
   css2Renderer.render(scene, camera);
  renderer.render(scene, camera);
  requestAnimationFrame(render);
}
javascript 复制代码
// 画布跟随窗口变化
window.onresize = function () {
  const width = window.innerWidth - 200; //canvas画布高度
  const height = window.innerHeight - 60; //canvas画布宽度
  renderer.setSize(width, height);
  css2Renderer.setSize(width, height);

  camera.aspect = width / height;

  camera.updateProjectionMatrix();

  // 其它CSS代码,与threejs无关
  document.getElementById("left").style.height = height + "px";
};

model.js

javascript 复制代码
const div = document.getElementById("tag");
// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);
tag.position.set(50, 0, 50);
model.add(tag);

四、标签位置不同设置方式

javascript 复制代码
import * as THREE from "three";

// 引入CSS2模型对象CSS2DObject
import { CSS2DObject } from "three/addons/renderers/CSS2DRenderer.js";

const group = new THREE.Group();

const geometry = new THREE.BoxGeometry(50, 50, 50);
const material = new THREE.MeshLambertMaterial({
  color: 0xffffff,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(50, 0, 50);

// mesh设置一个父对象meshGroup
const meshGroup = new THREE.Group();
meshGroup.add(mesh);
// mesh位置受到父对象局部坐标.positionn影响
meshGroup.position.x = -100;


const div = document.getElementById("tag");
// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);
tag.position.set(50, 0, 50);
group.add(meshGrouph, tag);

export default group;

.getWorldPosition()方法计算世界坐标

javascript 复制代码
const worldPosition = new THREE.Vector3();
// 获取mesh的世界坐标(meshGroup.position和mesh.position累加结果)
mesh.getWorldPosition(worldPosition);
// mesh世界坐标复制给tag
tag.position.copy(worldPosition);

CSS2模型对象作为Mesh子对象

javascript 复制代码
const group = new THREE.Group();

//标签tag作为mesh子对象,默认受到父对象位置影响
mesh.add(tag);

// group.add(meshGroup, tag);
group.add(meshGroup);

标注模型几何体的某个顶点

javascript 复制代码
const pos = geometry.attributes.position;
// 获取几何体顶点1的xyz坐标,设置标签局部坐标.position属性
tag.position.set(pos.getX(0),pos.getY(0),pos.getZ(0));

标注圆锥顶部(了解局部坐标系原点)

javascript 复制代码
const geometry = new THREE.ConeGeometry(25, 80);
const material = new THREE.MeshBasicMaterial({
  color: 0xffffff,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(50, 0, 50);
// 可视化模型的局部坐标系
const axesHelper = new THREE.AxesHelper(100);
mesh.add(axesHelper);

标签默认是标注在模型的局部坐标系坐标原点。

javascript 复制代码
//y轴正方向,平移高度一半
geometry.translate(0, 40, 0); 
//圆锥mesh局部坐标系原点在自己底部时候,标签需要向上偏移圆锥自身高度
tag.position.y += 80; 

沿着y方向平移-40,改变圆锥几何体顶点坐标,圆锥mesh的局部坐标系坐标原点此刻位于圆锥顶部,这样标签刚好标注在顶部。

五、标签位置(标注工厂设备)

threejs获取工厂设备,查看局部坐标系

javascript 复制代码
const obj = gltf.scene.getObjectByName('设备B');
// 可视化工厂设备obj的局部坐标系
const axesHelper = new THREE.AxesHelper(30);
obj.add(axesHelper);

CSS2模型对象标注工厂设备

javascript 复制代码
loader.load("../工厂.glb", function (gltf) {
    const tag = new CSS2DObject(div);
    // const obj = gltf.scene.getObjectByName('设备A');
    const obj = gltf.scene.getObjectByName('大货车1');
    //标签tag作为obj子对象,默认标注在工厂设备obj的局部坐标系坐标原点
    obj.add(tag);
})

建模软件创建空对象(控制标签位置)

在三维建模软件中,任何你想标注的位置,创建一个空对象(空的模型对象,没有任何模型顶点数据,只是一个空对象)。

不同三维建模软件中,创建空对象方式不同,不过思路是相同。

javascript 复制代码
// 单独.glb文件
loader.load("../../工厂.glb", function (gltf) {
  model.add(gltf.scene);
  const div = document.getElementById("tag");
  const tag = new CSS2DObject(div);

  // const obj = gltf.scene.getObjectByName("大货车1");
  const obj = gltf.scene.getObjectByName("空物体");
  // 可视化工厂设备obj的局部坐标系
  const axesHelper = new THREE.AxesHelper(30);
  obj.add(axesHelper);

  obj.add(tag);
});

六、标签指示线或箭头指向标注点

CSS2渲染器渲染HTML标签的位置特征

HTML元素标签自身的几何中心与标注点重合。

工厂设备标签CSS代码

javascript 复制代码
<!-- CSS布局方式写法很多,不一定和课程一致 -->
<div id="tag">
    <!-- position:relative;约束子元素绝对定位参照点 -->
    <div style="position:relative;width:400px;height:322px;color: #fff;">
        <!-- 图片绝对定位100%填充父元素,作为标签的背景 -->
        <img src="./信息背景.png" alt="" style="width:100%;position: absolute;left: 0px;top: 0px;">
        <!-- 名称、存储量、设备状态、等信息叠加到背景图上即可 -->
        <div style="position:absolute;left:48px;top:36px;font-size:16px;">
            <div style="font-size:20px;font-weight: 400;">
                <span>设备A</span>
            </div>
            <div style="margin-top: 30px;">
                <span style="font-weight: 400;margin-left: 80px;font-size: 40px;color: #00ffff;">276559 L</span>
            </div>
            <div style="margin-top: 20px;">
                <span style="color: #ccc;font-weight: 300;">管理</span><span
                   style="font-weight: 400;margin-left: 30px;">郭老师</span>
            </div>
            <div style="margin-top: 10px;">
                <span style="color: #ccc;font-weight: 300;">工号</span><span
                   style="font-weight: 400;margin-left: 30px;">webgl3d.cn</span>
            </div>
        </div>
        <div style="position:absolute;left:285px;top:35px;">
            <span style="color: #ffff00;">异常</span>
        </div>
    </div>
</div>

工厂标签的HTML元素适当平移

javascript 复制代码
div.style.top = '-161px'; //平移-161px,指示线端点和标注点重合

HTML标签渲染前隐藏

javascript 复制代码
style="display: none;"

七、鼠标选中模型弹出标签(工厂)

新建文件tag.js

javascript 复制代码
// 引入CSS2模型对象CSS2DObject
import {
    CSS2DObject
} from 'three/addons/renderers/CSS2DRenderer.js';
const div = document.getElementById('tag');
div.style.top = '-161px'; //指示线端点和标注点重合
// HTML元素转化为threejs的CSS2模型对象
const tag = new CSS2DObject(div);
export default tag;

在射线代码基础上,添加标签代码

javascript 复制代码
if (intersects.length > 0) {
    // 通过.ancestors属性判断那个模型对象被选中了
    outlinePass.selectedObjects = [intersects[0].object.ancestors];
    //tag会标注在intersects[0].object.ancestors模型的局部坐标系原点位置
    intersects[0].object.ancestors.add(tag);
}

由于局部坐标在中心

工厂模型添加一个空对象,用来标记需要标注的位置。

javascript 复制代码
if (intersects.length > 0) {
    // 通过.ancestors属性判断那个模型对象被选中了
    outlinePass.selectedObjects = [intersects[0].object.ancestors];
    // 获取模型对象对应的标注点
    // console.log('intersects[0].object.ancestors.name',intersects[0].object.ancestors.name);
    const obj = model.getObjectByName(intersects[0].object.ancestors.name+'标注');
    //tag会标注在空对象obj对应的位置
    obj.add(tag);
}

没有选中模型,不显示标签和发光描边

javascript 复制代码
// 存储当前选中的对象
let chooseObj = null;
javascript 复制代码
// 射线交叉计算拾取模型
  const intersects = raycaster.intersectObjects(cunchu.children);
  if (intersects.length > 0) {
    // 通过.ancestors属性判断那个模型对象被选中了
    outlinePass.selectedObjects = [intersects[0].object.ancestors];

    const obj = model.getObjectByName(
      intersects[0].object.ancestors.name + "标注"
    );
    obj.add(tag);

    chooseObj = obj;
  } else {
    if (chooseObj) {
      //把原来选中模型对应的标签和发光描边隐藏
      outlinePass.selectedObjects = []; //无发光描边
      chooseObj.remove(tag); //从场景移除
      chooseObj = null; // 重置选中对象
    }
  }

修改标签内容

javascript 复制代码
// 获取设备名称标签
const span = document.getElementById("name");
addEventListener("click", function (event) {
  ...
  // 射线交叉计算拾取模型
  const intersects = raycaster.intersectObjects(cunchu.children);
  if (intersects.length > 0) {
    // 通过.ancestors属性判断那个模型对象被选中了
    outlinePass.selectedObjects = [intersects[0].object.ancestors];

    const obj = model.getObjectByName(
      intersects[0].object.ancestors.name + "标注"
    );
    obj.add(tag);
    chooseObj = obj;

    span.innerHTML = intersects[0].object.ancestors.name;

  } else {
    if (chooseObj) {
      //把原来选中模型对应的标签和发光描边隐藏
      outlinePass.selectedObjects = []; //无发光描边
      chooseObj.remove(tag); //从场景移除
      chooseObj = null; // 重置选中对象
    }
  }
});

八、单击按钮关闭HTML标签

HTML标签增加一个关闭按钮

javascript 复制代码
<style>
    #close:hover {
        cursor: pointer;
    }
</style>
<div style="position:absolute;left:350px;top:20px;">
    <img id="close" src="./关闭.png"  width="32">
</div>

单击按钮关闭HTML标签

javascript 复制代码
// 鼠标单击按钮,关闭HTML标签
document.getElementById('close').addEventListener('click',function(){
    if (chooseObj) {//把原来选中模型对应的标签和发光描边隐藏
        outlinePass.selectedObjects = []; //无发光描边
        chooseObj.remove(tag); //从场景移除
    }
})

单击关闭按钮无效情况

HTML标签设置了属性.style.pointerEvents = 'none'

javascript 复制代码
<img id="close" src="./关闭.png" style="pointer-events: auto;">

or

javascript 复制代码
document.getElementById('close').style.pointerEvents = 'auto';

九、CSS3DRenderer渲染HTML标签

CSS3渲染的标签会跟着场景相机同步缩放,而CSS2渲染的标签默认保持自身像素值。

设置CSS3渲染器代码

javascript 复制代码
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
// 引入CSS2渲染器CSS2DRenderer
// import { CSS2DRenderer } from "three/addons/renderers/CSS2DRenderer.js";
// 引入CSS3渲染器CSS3DRenderer
import { CSS3DRenderer } from "three/addons/renderers/CSS3DRenderer.js";

import model from "./model.js"; //模型对象

//场景
const scene = new THREE.Scene();
scene.add(model); //模型对象添加到场景中

//辅助观察的坐标系
const axesHelper = new THREE.AxesHelper(100);
scene.add(axesHelper);

//光源设置
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(100, 60, 50);
scene.add(directionalLight);
const ambient = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambient);

//相机
const width = window.innerWidth;
const height = window.innerHeight;
// const width = 600;
// const height = 300;
const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
camera.position.set(292, 223, 185);
camera.lookAt(0, 0, 0);

// WebGL渲染器设置
const renderer = new THREE.WebGLRenderer({
  antialias: true, //开启优化锯齿
});
renderer.setPixelRatio(window.devicePixelRatio); //防止输出模糊
renderer.setSize(width, height);
document.body.appendChild(renderer.domElement);

// 创建一个CSS2渲染器CSS2DRenderer
// const css2Renderer = new CSS2DRenderer();
// width, height:canvas画布宽高度
// css2Renderer.setSize(width, height);
// document.body.appendChild(css2Renderer.domElement);
// css2Renderer.domElement.style.position = "absolute";
// css2Renderer.domElement.style.top = "0px";
// css2Renderer.domElement.style.pointerEvents = "none";

// 创建一个CSS3渲染器CSS3DRenderer
const css3Renderer = new CSS3DRenderer();
css3Renderer.setSize(width, height);
// HTML标签<div id="tag"></div>外面父元素叠加到canvas画布上且重合
css3Renderer.domElement.style.position = "absolute";
css3Renderer.domElement.style.top = "0px";
//设置.pointerEvents=none,解决HTML元素标签对threejs canvas画布鼠标事件的遮挡
css3Renderer.domElement.style.pointerEvents = "none";
document.body.appendChild(css3Renderer.domElement);



// 渲染循环
function render() {
  // 用法和webgl渲染器渲染方法类似
  // css2Renderer.render(scene, camera);
  css3Renderer.render(scene, camera);

  renderer.render(scene, camera);
  requestAnimationFrame(render);
}
render();

const controls = new OrbitControls(camera, renderer.domElement);

// 画布跟随窗口变化
window.onresize = function () {
   css3Renderer.setSize(width, height);
  renderer.setSize(window.innerWidth, window.innerHeight);
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
};

CSS3对象模型CSS3DObject

javascript 复制代码
import * as THREE from "three";

// 引入CSS2模型对象CSS2DObject
// import { CSS2DObject } from "three/addons/renderers/CSS2DRenderer.js";
// 引入CSS3模型对象CSS3DObject
import { CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';

const geometry = new THREE.ConeGeometry(25, 80);
const material = new THREE.MeshBasicMaterial({
  color: 0xffffff,
  transparent: true,
  opacity: 0.5,
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(50, 0, 50);
// 可视化模型的局部坐标系
const axesHelper = new THREE.AxesHelper(100);
mesh.add(axesHelper);

// mesh设置一个父对象meshGroup
const meshGroup = new THREE.Group();
meshGroup.add(mesh);
// mesh位置受到父对象局部坐标.positionn影响
meshGroup.position.x = -100;

// const div = document.getElementById("tag");
// // HTML元素转化为threejs的CSS2模型对象
// const tag = new CSS2DObject(div);
// //y轴正方向,平移高度一半
// geometry.translate(0, 40, 0); 
// //圆锥mesh局部坐标系原点在自己底部时候,标签需要向上偏移圆锥自身高度
// tag.position.y += 80; 
const div = document.getElementById("tag");
// HTML元素转化为threejs的CSS3模型对象
const tag = new CSS3DObject(div);
//标签tag作为mesh子对象,默认标注在模型局部坐标系坐标原点
mesh.add(tag);
// 相对父对象局部坐标原点偏移80,刚好标注在圆锥
tag.position.y += 80;

const group = new THREE.Group();

//标签tag作为mesh子对象,默认受到父对象位置影响
mesh.add(tag);

// group.add(meshGroup, tag);
group.add(meshGroup);

export default group;

禁止CSS3DObject标签对应HTMl元素背面显示

javascript 复制代码
style="backface-visibility: hidden;"

CSS3精灵模型CSS3DSprite

javascript 复制代码
// 引入CSS3精灵模型对象CSS3DSprite
import { CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';

const div = document.getElementById('tag');
// HTML元素转化为threejs的CSS3精灵模型`CSS3DSprite`
const tag = new CSS3DSprite(div);
//标签tag作为mesh子对象,默认标注在模型局部坐标系坐标原点
mesh.add(tag);
// 相对父对象局部坐标原点偏移80,刚好标注在圆锥
tag.position.y += 80;

标签局部遮挡鼠标事件

javascript 复制代码
css3Renderer.domElement.style.pointerEvents = "none";

虽然css3Renderer.domElement不遮挡canvas画布的鼠标事件,但是<div id="tag"></div>遮挡canvas画布的鼠标事件

model.js

javascript 复制代码
div.style.pointerEvents = 'none';

十、CSS3批量标注多个标签

CSS3渲染器基本代码

tag.js

javascript 复制代码
// 引入CSS2模型对象CSS2DObject
import { CSS3DSprite } from "three/examples/jsm/renderers/CSS3DSprite.js";
const div = document.getElementById("tag");
// HTML元素转化为threejs的CSS3对象
// const tag = new CSS3DObject(div);
const tag = new CSS3DSprite(div);
div.style.pointerEvents = "none"; //避免标签遮挡canvas鼠标事件
// obj是建模软件中创建的一个空对象
const obj = gltf.scene.getObjectByName("设备A标注");
//tag会标注在空对象obj对应的位置
obj.add(tag);

export default tag;

标签HTML、CSS代码

javascript 复制代码
<style>
    #tag {
        width: 70px;
        height: 40px;
        line-height: 32px;
        text-align: center;
        color: #fff;
        font-size: 16px;
        background-image: url(./标签箭头背景.png);
        background-repeat: no-repeat;
        background-size: 100% 100%;
    }
</style>
<div id="tag">设备A</div>
javascript 复制代码
//需要批量标注的标签数据arr
const arr = ['设备A','设备B','停车场'];
for (let i = 0; i < arr.length; i++) {
    // 注意是多个标签,需要克隆复制一份
    const div = document.getElementById('tag').cloneNode();
    div.innerHTML = arr[i];//标签数据填写
    // HTML元素转化为threejs的CSS3对象
    // const tag = new CSS3DObject(div);
    const tag = new CSS3DSprite(div);
    div.style.pointerEvents = 'none'; //避免标签遮挡canvas鼠标事件
    // obj是建模软件中创建的一个空对象
    const obj = gltf.scene.getObjectByName(arr[i]+'标注');
    //tag会标注在空对象obj对应的位置
    obj.add(tag);

    tag.scale.set(0.1,0.1,1);//适当缩放模型标签
    tag.position.y = 40/2*0.1;//标签底部箭头和空对象标注点重合:偏移高度像素值一半*缩放比例
}

十一、精灵模型Sprite作为标签

精灵模型标签

javascript 复制代码
  const texLoader = new THREE.TextureLoader();
  const texture = texLoader.load("./警告.png");
  const spriteMaterial = new THREE.SpriteMaterial({
    map: texture,
  });
  const sprite = new THREE.Sprite(spriteMaterial);
  sprite.scale.set(5, 5, 1);
  sprite.position.y = 5 / 2; //标签底部箭头和空对象标注点重合
  // obj是建模软件中创建的一个空对象
  const obj = gltf.scene.getObjectByName("设备A标注");
  //tag会标注在空对象obj对应的位置
  obj.add(sprite);

创建sprite.js

javascript 复制代码
import * as THREE from "three";
function createSprite(obj, state) {
  const texLoader = new THREE.TextureLoader();
  let texture = null;

  if (state == "警告") {
    texture = texLoader.load("./警告.png");
  } else {
    texture = texLoader.load("./提示.png");
  }
  const spriteMaterial = new THREE.SpriteMaterial({
    map: texture,
  });
  const sprite = new THREE.Sprite(spriteMaterial);
  sprite.scale.set(5, 5, 1);
  sprite.position.y = 5 / 2; //标签底部箭头和空对象标注点重合
  // obj是建模软件中创建的一个空对象
  const obj = gltf.scene.getObjectByName("设备A标注");
  //tag会标注在空对象obj对应的位置
  obj.add(sprite);
}
export default createSprite;

十二、Sprite标签(Canvas作为贴图)

创建canvas.js

javascript 复制代码
// 生成一个canvas对象,标注文字为参数name
function createCanvas(name) {
  /**
   * 创建一个canvas对象,绘制几何图案或添加文字
   */
  const canvas = document.createElement("canvas");
  const arr = name.split(""); //分割为单独字符串
  let num = 0;
  const reg = /[\u4e00-\u9fa5]/;
  for (let i = 0; i < arr.length; i++) {
    if (reg.test(arr[i])) {
      //判断是不是汉字
      num += 1;
    } else {
      num += 0.5; //英文字母或数字累加0.5
    }
  }
  // 根据字符串符号类型和数量、文字font-size大小来设置canvas画布宽高度
  const h = 80; //根据渲染像素大小设置,过大性能差,过小不清晰
  const w = h + num * 32;
  canvas.width = w;
  canvas.height = h;
  const h1 = h * 0.8;
  const c = canvas.getContext("2d");
  // 定义轮廓颜色,黑色半透明
  c.fillStyle = "rgba(0,0,0,0.5)";
  // 绘制半圆+矩形轮廓
  const R = h1 / 2;
  c.arc(R, R, R, -Math.PI / 2, Math.PI / 2, true); //顺时针半圆
  c.arc(w - R, R, R, Math.PI / 2, -Math.PI / 2, true); //顺时针半圆
  c.fill();
  // 绘制箭头
  c.beginPath();
  const h2 = h - h1;
  c.moveTo(w / 2 - h2 * 0.6, h1);
  c.lineTo(w / 2 + h2 * 0.6, h1);
  c.lineTo(w / 2, h);
  c.fill();
  // 文字
  c.beginPath();
  c.translate(w / 2, h1 / 2);
  c.fillStyle = "#ffffff"; //文本填充颜色
  c.font = "normal 32px 宋体"; //字体样式设置
  c.textBaseline = "middle"; //文本与fillText定义的纵坐标
  c.textAlign = "center"; //文本居中(以fillText定义的横坐标)
  c.fillText(name, 0, 0);
  return canvas;
}
export default createCanvas;

model.js

javascript 复制代码
// 引入Three.js
import * as THREE from "three";
// 引入gltf模型加载库GLTFLoader.js
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
import createCanvas from "./canvas.js";

const loader = new GLTFLoader(); //创建一个GLTF加载器
const model = new THREE.Group(); //声明一个组对象,用来添加加载成功的三维场景
// 单独.glb文件
loader.load("../../工厂.glb", function (gltf) {
  model.add(gltf.scene);
  const canvas = createCanvas("设备A");
  // canvas画布作为CanvasTexture的参数创建一个纹理对象
  // 本质上你可以理解为CanvasTexture读取参数canvas画布上的像素值
  const texture = new THREE.CanvasTexture(canvas);
  const spriteMaterial = new THREE.SpriteMaterial({
    map: texture,
  });
  const sprite = new THREE.Sprite(spriteMaterial);
  const y = 4; //精灵y方向尺寸
  // sprite宽高比和canvas画布保持一致
  const x = (canvas.width / canvas.height) * y; //精灵x方向尺寸
  sprite.scale.set(x, y, 1); // 控制精灵大小
  sprite.position.y = y / 2; //标签底部箭头和空对象标注点重合
  const obj = gltf.scene.getObjectByName("设备A标注"); // obj是建模软件中创建的一个空对象
  obj.add(sprite); //tag会标注在空对象obj对应的位置
});

export default model;

cavnas精灵标签封装(标注多个)

Canvas包含外部图片

要等图像加载完成再执行THREE.CanvasTexture(canvas)

Threejs (16)

一、关键帧动画

创建关键帧动画AnimationClip

模型命名

javascript 复制代码
// 给需要设置关键帧动画的模型命名
mesh1.name = "Box";

KeyframeTrack设置关键帧数据

javascript 复制代码
const times = [0, 3, 6]; //时间轴上,设置三个时刻0、3、6秒
// times中三个不同时间点,物体分别对应values中的三个xyz坐标
const values = [0, 0, 0, 100, 0, 0, 0, 0, 100];
// 0~3秒,物体从(0,0,0)逐渐移动到(100,0,0),3~6秒逐渐从(100,0,0)移动到(0,0,100)
const posKF = new THREE.KeyframeTrack("Box.position", times, values);
// 从2秒到5秒,物体从红色逐渐变化为蓝色
const colorKF = new THREE.KeyframeTrack(
  "Box.material.color",
  [2, 5],
  [1, 0, 0, 0, 0, 1]
);

创建关键帧动画AnimationClip

javascript 复制代码
// 1.3 AnimationClip表示一个关键帧动画,可以基于关键帧数据产生动画效果
// 创建一个clip关键帧动画对象,命名"test",动画持续时间6s
// AnimationClip包含的所有关键帧数据都放到参数3数组中即可
const clip = new THREE.AnimationClip("test",6,[posKF, colorKF]);

model.js

javascript 复制代码
// 给需要设置关键帧动画的模型命名
mesh.name = "Box";
const times = [0, 3, 6]; //时间轴上,设置三个时刻0、3、6秒
// times中三个不同时间点,物体分别对应values中的三个xyz坐标
const values = [0, 0, 0, 100, 0, 0, 0, 0, 100];
// 0~3秒,物体从(0,0,0)逐渐移动到(100,0,0),3~6秒逐渐从(100,0,0)移动到(0,0,100)
const posKF = new THREE.KeyframeTrack('Box.position', times, values);
// 从2秒到5秒,物体从红色逐渐变化为蓝色
const colorKF = new THREE.KeyframeTrack('Box.material.color', [2, 5], [1, 0, 0, 0, 0, 1]);
// 1.3 基于关键帧数据,创建一个clip关键帧动画对象,命名"test",持续时间6秒。
const clip = new THREE.AnimationClip("test", 6, [posKF, colorKF]);

AnimationMixer播放关键帧动画AnimationClip

javascript 复制代码
//包含关键帧动画的模型对象作为AnimationMixer的参数创建一个播放器mixer
const mixer = new THREE.AnimationMixer(mesh1);

//AnimationMixer的`.clipAction()`返回一个AnimationAction对象
const clipAction = mixer.clipAction(clip); 
//.play()控制动画播放,默认循环播放
clipAction.play(); 

mixer.update()更新播放器AnimationMixer时间

javascript 复制代码
function loop() {
    requestAnimationFrame(loop);
}
loop();

二、动画播放(暂停、倍速、循环)

动画动作对象AnimationAction

AnimationAction的循环属性.loop
javascript 复制代码
//不循环播放
clipAction.loop = THREE.LoopOnce; 
AnimationAction.clampWhenFinished属性
javascript 复制代码
// 物体状态停留在动画结束的时候
clipAction.clampWhenFinished = true;
停止结束动画.stop()
javascript 复制代码
<div class="pos">
    <div id="stop" class="bu">停止</div>
    <div id="play" class="bu" style="margin-left: 10px;">播放</div>
</div>

model.js

javascript 复制代码
document.getElementById('stop').addEventListener('click',function(){
  clipAction.stop();//动画停止结束,回到开始状态
})
document.getElementById('play').addEventListener('click',function(){
  clipAction.play();//播放动画
})

是否暂停播放.paused

javascript 复制代码
<div id="bu" class="bu">暂停</div>
javascript 复制代码
const bu = document.getElementById('bu');
bu.addEventListener('click',function(){
    // AnimationAction.paused默认值false,设置为true,可以临时暂停动画
    if (clipAction.paused) {//暂停状态
        clipAction.paused = false;//切换为播放状态
        bu.innerHTML='暂停';// 如果改变为播放状态,按钮文字设置为"暂停"
      } else {//播放状态
        clipAction.paused = true;//切换为暂停状态
        bu.innerHTML='继续';// 如果改变为暂停状态,按钮文字设置为"继续"
      }
})

倍速播放.timeScale

javascript 复制代码
clipAction.timeScale = 1;//默认
clipAction.timeScale = 2;//2倍速

拖动条调整播放速度

javascript 复制代码
const gui = new GUI(); //创建GUI对象
// 0~6倍速之间调节
gui.add(clipAction, 'timeScale', 0, 6);

三、动画播放(拖动任意时间状态)

控制动画播放特定时间段

javascript 复制代码
//AnimationAction设置开始播放时间:从1秒时刻对应动画开始播放
clipAction.time = 1; 
//AnimationClip设置播放结束时间:到5秒时刻对应的动画状态停止
clip.duration = 5;

查看时间轴上任意时间动画状态

javascript 复制代码
//在暂停情况下,设置.time属性,把动画定位在任意时刻
clipAction.paused = true;

拖动条拖动显示动画任意时刻模型状态

javascript 复制代码
//在暂停情况下,设置.time属性,把动画定位在任意时刻
clipAction.paused = true;

拖动条拖动显示动画任意时刻模型状态

javascript 复制代码
import {GUI} from 'three/addons/libs/lil-gui.module.min.js';
const gui = new GUI(); //创建GUI对象
gui.add(clipAction, 'time', 0, 6).step(0.1);;

动画下一步状态

下一步按钮

javascript 复制代码
<div id="bu" class="bu">下一步</div>
javascript 复制代码
const bu = document.getElementById('bu');
bu.addEventListener('click', function () {
  clipAction.time += 0.1; 
})

四、解析外部模型关键帧动画

查看gltf模型动画数据

javascript 复制代码
 console.log('控制台查看gltf对象结构', gltf);

播放AnimationClip动画

javascript 复制代码
//包含关键帧动画的模型作为参数创建一个播放器
    const mixer = new THREE.AnimationMixer(gltf.scene);
    //  获取gltf.animations[0]的第一个clip动画对象
    const clipAction = mixer.clipAction(gltf.animations[0]); //创建动画clipAction对象
    clipAction.play(); //播放动画

    // 如果想播放动画,需要周期性执行`mixer.update()`更新AnimationMixer时间数据
    const clock = new THREE.Clock();
    function loop() {
        requestAnimationFrame(loop);
        //clock.getDelta()方法获得loop()两次执行时间间隔
        const frameT = clock.getDelta();
        // 更新播放器相关的时间
        mixer.update(frameT);
    }
    loop();

动画是否循环播放

人走路、跑步美术美术一般设置很短时间运动,如果你想一直看到运动动作,不用设置非循环。

javascript 复制代码
//不循环播放
clipAction.loop = THREE.LoopOnce; 
// 物体状态停留在动画结束的时候
clipAction.clampWhenFinished = true

五、机械虚拟装配案例(播放)

按钮控制虚拟装配播放、暂停

javascript 复制代码
const bu = document.getElementById('bu');
bu.addEventListener('click',function(){
    // AnimationAction.paused默认值false,设置为true,可以临时暂停动画
    if (clipAction.paused) {//暂停状态
        clipAction.paused = false;//切换为播放状态
        bu.innerHTML='暂停';// 如果改变为播放状态,按钮文字设置为"暂停"
      } else {//播放状态
        clipAction.paused = true;//切换为暂停状态
        bu.innerHTML='播放';// 如果改变为暂停状态,按钮文字设置为"播放"
      }
})

动画播放结束,按钮样式恢复到播放

javascript 复制代码
  clipAction.loop = THREE.LoopOnce;
  // 动画播放完成事件
  mixer.addEventListener("finished", function () {
    bu.innerHTML = "播放"; //播放完成,按钮显示为"播放"
    clipAction.reset(); //重新开始新的动画播放
    clipAction.paused = true; //切换为暂停状态
  });

拖动条控制播放倍速

javascript 复制代码
import {GUI} from 'three/addons/libs/lil-gui.module.min.js';
const gui = new GUI(); //创建GUI对象
// 0~2倍速之间调节
gui.add(clipAction, 'timeScale', 0, 2).step(0.1).name('倍速');

六、虚拟装配(任意时间定位)

属性.duration获取动画默认的执行时间

javascript 复制代码
const duration = clip.duration;//默认持续时间

拖动条查看任意时间动画状态

javascript 复制代码
// 拖动条查看动画任何时刻模型状态
gui.add(clipAction,'time',0,duration).step(0.1).name('拖动');

拖动条与播放按钮功能组合

javascript 复制代码
 // 拖动条查看动画任何时刻模型状态
  gui
    .add(clipAction, "time", 0, duration)
    .step(0.1)
    .name("拖动")
    .onChange(function () {
      //如果动画处于播放状态会影响拖动条时间定位
      if (!clipAction.paused) {
        clipAction.paused = true; //切换为暂停状态
        bu.innerHTML = "播放"; //修改按钮样式
      }
    });

七、变形动画原理

.morphAttributes设置几何体变形目标顶点数据

javascript 复制代码
//几何体两组顶点一一对应,位置不同,然后通过权重系数,可以控制模型形状在两组顶点之间变化
const geometry = new THREE.BoxGeometry(50, 50, 50);
// 为geometry提供变形目标的顶点数据(注意和原始geometry顶点数量一致)
const target1 = new THREE.BoxGeometry(50, 200, 50).attributes.position;//变高
const target2 = new THREE.BoxGeometry(10, 50, 10).attributes.position;//变细
// 几何体顶点变形目标数据,可以设置1组或多组
geometry.morphAttributes.position = [target1, target2];

const mesh = new THREE.Mesh(geometry, material);

.morphTargetInfluences权重系数控制变形程度

javascript 复制代码
//权重0:物体形状对应geometry.attributes.position表示形状
mesh.morphTargetInfluences[0] = 0.0;
//权重1:物体形状对应target1表示形状
mesh.morphTargetInfluences[0] = 1.0;
//权重0.5:物体形状对应geometry和target1变形中间状态
mesh.morphTargetInfluences[0] = 0.5;

多个变形目标综合影响模型形状

javascript 复制代码
// 两个变形目标同时影响模型形状
mesh.morphTargetInfluences[1] = 0.5;
mesh.morphTargetInfluences[0] = 0.5;

GUI控制变形权重系数.morphTargetInfluences

javascript 复制代码
import {GUI} from 'three/addons/libs/lil-gui.module.min.js';
const gui = new GUI(); 
// GUI拖动条可视化改变变形目标权重系数
const obj = {
    t1: 0,
    t2: 0,
}
gui.add(obj, 't1', 0, 1).name('变形目标1').onChange(function (v) {
    // 变形目标1对物体形状影响权重
    mesh.morphTargetInfluences[0] = v;
});
gui.add(obj, 't2', 0, 1).name('变形目标2').onChange(function (v) {
    // 变形目标2对物体形状影响权重
    mesh.morphTargetInfluences[1] = v;
});

生成变形动画

javascript 复制代码
// 创建变形动画权重系数的关键帧数据
mesh.name = "Box";//关键帧动画控制的模型对象命名
// 设置变形目标1对应权重随着时间的变化
const KF1 = new THREE.KeyframeTrack('Box.morphTargetInfluences[0]', [0, 5], [0, 1]);
// 设置变形目标2对应权重随着时间的变化
const KF2 = new THREE.KeyframeTrack('Box.morphTargetInfluences[1]', [5, 10], [0, 1]);
// 创建一个剪辑clip对象
const clip = new THREE.AnimationClip("t", 10, [KF1, KF2]);

八、变形动画

查看模型几何体变形相关信息

javascript 复制代码
  // 访问网格模型
  const mesh = gltf.scene.children[0];
  // 获取所有变形目标的顶点数据
  const tArr = mesh.geometry.morphAttributes.position;
  console.log("所有变形目标", tArr);
  console.log("所有权重", mesh.morphTargetInfluences);

UI界面定制

javascript 复制代码
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
const gui = new GUI();

批量设置所有变形目标的拖动条

javascript 复制代码
loader.load("../../猴头.glb", function (gltf) {
  console.log("控制台查看gltf对象结构", gltf);
  model.add(gltf.scene);
  // 访问网格模型
  const mesh = gltf.scene.children[0];
  // 获取所有变形目标的顶点数据
  const tArr = mesh.geometry.morphAttributes.position;
  console.log("所有变形目标", tArr);
  console.log("所有权重", mesh.morphTargetInfluences);

  // 每个变形目标对应的含义(注意和变形目标对应起来)
  const nameArr = ["耳朵变尖", "头顶变高", "眉毛变高"];
  // GUI拖动条可视化改变变形目标权重系数
  const obj = {};
  for (let i = 0; i < tArr.length; i++) {
    obj["t" + i] = 0; //obj批量定义一个属性表示变性目标的权重系数
    //   // 批量设置要改变的obj属性,对应name名字,和对应权重
    gui
      .add(obj, "t" + i, 0, 1)
      .name(nameArr[i])
      .onChange(function (v) {
        mesh.morphTargetInfluences[i] = v;
      });
  }
});

外部模型变形数据生成动画

javascript 复制代码
  // 创建变形动画权重系数的关键帧数据
  mesh.name = "per"; //关键帧动画控制的模型对象命名
  // 设置变形目标1对应权重随着时间的变化
  const KF1 = new THREE.KeyframeTrack(
    "per.morphTargetInfluences[0]",
    [0, 5],
    [0, 1]
  );
  // 生成关键帧动画
  const clip = new THREE.AnimationClip("t", 5, [KF1]);

  //包含关键帧动画的模型作为参数创建一个播放器
  const mixer = new THREE.AnimationMixer(gltf.scene);
  const clipAction = mixer.clipAction(clip);
  clipAction.play();

  const clock = new THREE.Clock();
  function loop() {
    requestAnimationFrame(loop);
    const frameT = clock.getDelta();
    // 更新播放器相关的时间
    mixer.update(frameT);
  }
  loop();

九、骨骼关节Bone

骨骼关节Bone树结构

javascript 复制代码
const Bone1 = new THREE.Bone(); //关节1,用来作为根关节
const Bone2 = new THREE.Bone(); //关节2
const Bone3 = new THREE.Bone(); //关节3

// 设置关节父子关系   多个骨头关节构成一个树结构
Bone1.add(Bone2);
Bone2.add(Bone3);

设置关节模型的位置和姿态角度

javascript 复制代码
//根关节Bone1默认位置是(0,0,0)
Bone2.position.y = 60; //Bone2相对父对象Bone1位置
Bone3.position.y = 30; //Bone3相对父对象Bone2位置
//平移Bone1,Bone2、Bone3跟着平移
Bone1.position.set(50, 0, 50);
// 骨骼关节旋转
Bone1.rotateX(Math.PI / 6);
Bone2.rotateX(Math.PI / 6);
javascript 复制代码
// 骨骼关节可以和普通网格模型一样作为其他模型子对象,添加到场景中
const group = new THREE.Group();
group.add(Bone1);

// SkeletonHelper会可视化参数模型对象所包含的所有骨骼关节
const skeletonHelper = new THREE.SkeletonHelper(group);
group.add(skeletonHelper);

拖动条控制骨骼关节旋转

javascript 复制代码
import {GUI} from 'three/addons/libs/lil-gui.module.min.js';
const gui = new GUI();
gui.add(Bone1.rotation, 'x', 0, Math.PI / 3).name('关节1');
gui.add(Bone2.rotation, 'x', 0, Math.PI / 3).name('关节2');

十、查看外部模型骨骼动画

可视化外部模型骨骼关节

javascript 复制代码
import * as THREE from "three";
// 引入gltf模型加载库GLTFLoader.js
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
const loader = new GLTFLoader();
const model = new THREE.Group();
loader.load("../../骨骼动画.glb", function (gltf) {
  console.log("控制台查看gltf对象结构", gltf);
  model.add(gltf.scene);
  // 骨骼辅助显示
  const skeletonHelper = new THREE.SkeletonHelper(gltf.scene);
  model.add(skeletonHelper);
});

export default model;

根据骨骼名称读取骨骼关节

javascript 复制代码
// 根据骨骼关节名字获取骨关节Bone  
// 在三维软件中,骨骼关节层层展开,可以看到下面三个骨骼关节
const bone1 = gltf.scene.getObjectByName('Bone1'); //关节1
const bone2 = gltf.scene.getObjectByName('Bone2'); //关节2
const bone3 = gltf.scene.getObjectByName('Bone3'); //关节3

代码测试骨骼关节Bone带动模型表面变化

javascript 复制代码
bone2.rotation.x = Math.PI / 6; //关节2旋转
bone3.rotation.x = Math.PI / 6; //关节3旋转

查看骨骼网格模型SkinnedMesh

javascript 复制代码
  // 根据节点名字获取某个骨骼网格模型
  const SkinnedMesh = gltf.scene.getObjectByName("");
  console.log("骨骼网格模型", SkinnedMesh);

访问骨骼网格模型的骨架SkinnedMesh.skeleton

javascript 复制代码
console.log('骨架', SkinnedMesh.skeleton);

骨架的骨骼关节属性.skeleton.bones

javascript 复制代码
console.log('骨架所有关节', SkinnedMesh.skeleton.bones);
console.log('根关节', SkinnedMesh.skeleton.bones[0]);

播放骨骼网格模型的关键帧动画

javascript 复制代码
import * as THREE from "three";
// 引入gltf模型加载库GLTFLoader.js
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
const loader = new GLTFLoader();
const model = new THREE.Group();
loader.load("../../骨骼动画.glb", function (gltf) {
  console.log("控制台查看gltf对象结构", gltf);
  model.add(gltf.scene);
  // 骨骼辅助显示
  // const skeletonHelper = new THREE.SkeletonHelper(gltf.scene);
  // model.add(skeletonHelper);

  // 根据骨骼关节名字获取骨关节Bone
  // 在三维软件中,骨骼关节层层展开,可以看到下面三个骨骼关节
  // const bone1 = gltf.scene.getObjectByName("mixamorig:Hips");
  // const bone2 = gltf.scene.getObjectByName("Ctrl_Hand_IK_Left"); //关节2
  // const bone3 = gltf.scene.getObjectByName("Ctrl_ArmPole_IK_Right"); //关节3
  // console.log("关节1", bone1);
  // console.log("关节2", bone2);
  // console.log("关节3", bone3);

  // bone1.rotation.x = Math.PI / 6; //关节1旋转
  // bone2.rotation.x = Math.PI / 6; //关节2旋转
  // bone3.rotation.x = Math.PI / 6; //关节3旋转

  // 根据节点名字获取某个骨骼网格模型
  // const SkinnedMesh = gltf.scene.getObjectByName("mixamorig:Head");
  // console.log("骨骼网格模型", SkinnedMesh);
  // console.log("骨架", SkinnedMesh.skeleton);
  //包含关键帧动画的模型作为参数创建一个播放器
  const mixer = new THREE.AnimationMixer(gltf.scene);
  // gltf.animations[0]休息
  // gltf.animations[1]休息
  // gltf.animations[2]走路
  // gltf.animations[3]跑
  const clipAction = mixer.clipAction(gltf.animations[3]);
  clipAction.play(); //播放动画
  // 如果想播放动画,需要周期性执行`mixer.update()`更新AnimationMixer时间数据
  const clock = new THREE.Clock();
  function loop() {
    requestAnimationFrame(loop);
    //clock.getDelta()方法获得loop()两次执行时间间隔
    const frameT = clock.getDelta();
    // 更新播放器相关的时间
    mixer.update(frameT);
  }
  loop();
});

export default model;

十一、骨骼动画不同动作切换

切换动画不同动作(.play().stop())

javascript 复制代码
    #pos {
            position: absolute;
            top: 75%;
            left: 50%;
            color: aliceblue;
        }
javascript 复制代码
    <div id="pos">
        <div id="Idle1"
            class="bu1">休息1</div>
        <div id="Idle2"
            class="bu2">休息2</div>
        <div id="Run"
            class="bu"
            style="margin-left: 10px;">跑步</div>
        <div id="Walk"
            class="bu"
            style="margin-left: 10px;">走路</div>
    </div>

点击按钮,按钮对应的动作对象AnimationAction,执行.play()方法开始动画执行,原来执行中的动画动作对象,执行.stop()方法终止执行。

javascript 复制代码
import * as THREE from "three";
// 引入gltf模型加载库GLTFLoader.js
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
const loader = new GLTFLoader();
const model = new THREE.Group();
loader.load("../../骨骼动画.glb", function (gltf) {
  model.add(gltf.scene);

  //包含关键帧动画的模型作为参数创建一个播放器
  const mixer = new THREE.AnimationMixer(gltf.scene);
  // gltf.animations[0]休息
  // gltf.animations[1]休息
  // gltf.animations[2]走路
  // gltf.animations[3]跑


  const IdleAction = mixer.clipAction(gltf.animations[0]);
  const IdleAction1 = mixer.clipAction(gltf.animations[1]);
  const RunAction = mixer.clipAction(gltf.animations[3]);
  const WalkAction = mixer.clipAction(gltf.animations[2]);
  IdleAction.play();

  let ActionState = IdleAction; //当前处于播放状态的动画动作对象

  // 通过UI按钮控制,切换动画运动状态
  document.getElementById("Idle1").addEventListener("click", function () {
    ActionState.stop(); //播放状态动画终止
    IdleAction.play();
    ActionState = IdleAction;
  });
  document.getElementById("Idle2").addEventListener("click", function () {
    ActionState.stop();
    IdleAction1.play();
    ActionState = IdleAction1;
  });
  document.getElementById("Run").addEventListener("click", function () {
    ActionState.stop(); //播放状态动画终止
    RunAction.play();
    ActionState = RunAction;
  });
  document.getElementById("Walk").addEventListener("click", function () {
    ActionState.stop(); //播放状态动画终止
    WalkAction.play();
    ActionState = WalkAction;
  });

  // 如果想播放动画,需要周期性执行`mixer.update()`更新AnimationMixer时间数据
  const clock = new THREE.Clock();
  function loop() {
    requestAnimationFrame(loop);
    //clock.getDelta()方法获得loop()两次执行时间间隔
    const frameT = clock.getDelta();
    // 更新播放器相关的时间
    mixer.update(frameT);
  }
  loop();
});

export default model;

AnimationAction的权重属性.weight

javascript 复制代码
  IdleAction.play();
  RunAction.play();
  WalkAction.play();
javascript 复制代码
  // 跑步和走路动画对人影响程度为0,人处于休闲状态
  IdleAction.weight = 1.0;
  RunAction.weight = 0.0;
  WalkAction.weight = 0.0;

切换动画不同动作(.weight)

javascript 复制代码
  IdleAction.play();
  IdleAction1.play();
  RunAction.play();
  WalkAction.play();

  // 跑步和走路动画对人影响程度为0,人处于休闲状态
  IdleAction.weight = 1.0;
  IdleAction1.weight = 0.0;
  RunAction.weight = 0.0;
  WalkAction.weight = 0.0;

  let ActionState = IdleAction; //当前处于播放状态的动画动作对象

  // 通过UI按钮控制,切换动画运动状态
  document.getElementById("Idle1").addEventListener("click", function () {
    ActionState.weight = 0.0; //播放状态动画权重设置为0
    IdleAction.weight = 1.0;
    ActionState = IdleAction;
  });
  document.getElementById("Idle2").addEventListener("click", function () {
    ActionState.weight = 0.0; //播放状态动画权重设置为0
    IdleAction1.weight = 1.0;
    ActionState = IdleAction;
  });
  document.getElementById("Run").addEventListener("click", function () {
    ActionState.weight = 0.0; //播放状态动画权重设置为0
    RunAction.weight = 1.0;
    ActionState = RunAction;
  });
  document.getElementById("Walk").addEventListener("click", function () {
    ActionState.weight = 0.0; //播放状态动画权重设置为0
    WalkAction.weight = 1.0;
    ActionState = WalkAction;
  });
相关推荐
知识分享小能手2 小时前
Oracle 19c入门学习教程,从入门到精通,Oracle 过程、函数、触发器和包详解(7)
数据库·学习·oracle
漏刻有时2 小时前
微信小程序学习实录14:微信小程序手写签名功能完整开发方案
学习·微信小程序·notepad++
魔芋红茶2 小时前
Spring Security 学习笔记 1:快速开始
笔记·学习·spring
皮蛋sol周2 小时前
嵌入式学习数据结构(三)栈 链式 循环队列
arm开发·数据结构·学习·算法··循环队列·链式队列
Charlie_lll3 小时前
学习Three.js–材质(Material)
前端·three.js
Kratzdisteln3 小时前
【1902】优化后的三路径学习系统
android·学习
仰泳之鹅3 小时前
【PID学习】多环PID
学习·pid
testpassportcn3 小时前
CompTIA A+ 220-1201 認證介紹|CompTIA A+ Core 1 考試內容、題型與高效備考指南
网络·学习·改行学it
2501_944934733 小时前
数据洞察力:职业转型的核心竞争力
学习