Three.js基础入门介绍——Three.js学习六【模型动画】

news2025/2/24 20:40:20

Three.js动画系统(Animation system)

让模型实现动画效果,需要使用Three动画系统,动画系统官方文档链接,这里稍作了修改。

动画系统(Animation system)概述:
在three.js动画系统中,您可以为模型的各种属性设置动画: SkinnedMesh(蒙皮和装配模型)的骨骼,morph targets(变形目标), 不同的材料属性(颜色,不透明度,布尔运算),可见性和变换。动画属性可以淡入、淡出、交叉淡化和扭曲。 在相同或不同物体上同时发生的动画的权重和时间比例的变化可以独立地进行。 相同或不同物体的动画也可以同步发生。

组件作用
动画片段(Animation Clips)成功导入3D动画对象的响应字段中包含一个名为“animations”的数组, 其中包含此模型的AnimationClips(请参阅下面可用的加载器列表)。 每个AnimationClip通常保存对象某个活动的数据, 举个例子,假如mesh是一个角色,可能有一个AnimationClip实现步行循环, 第二个AnimationClip实现跳跃,第三个AnimationClip实现闪避等等。
关键帧轨道(Keyframe Tracks)在AnimationClip中,每个动画属性的数据都存储在一个单独的KeyframeTrack中。假设一个角色对象有Skeleton(骨架), 一个关键帧轨道可以存储下臂骨骼位置随时间变化的数据, 另一个轨道追踪同一块骨骼的旋转变化,第三个追踪另外一块骨骼的位置、转角和尺寸,等等。AnimationClip可以由许多这样的轨道组成。假设模型具有morph Targets(变形目标)—— 例如一个变形目标显示一个笑脸,另一个显示愤怒的脸 —— 每个轨道都持有某个变形目标在AnimationClip运行期间产生的Mesh.morphTargetInfluences(变形目标影响)如何变化的信息。
动画混合器(Animation Mixer)存储的数据仅构成动画的基础 —— 实际播放由AnimationMixer控制。可以同时控制和混合若干动画。
动画行为(Animation Actions)AnimationMixer本身只有很少的(大体上)属性和方法, 因为它可以通过AnimationActions来控制。 通过配置AnimationAction,您可以决定何时播放、暂停或停止其中一个混合器中的某个AnimationClip, 这个AnimationClip是否需要重复播放以及重复的频率, 是否需要使用淡入淡出或时间缩放,以及一些其他内容(例如交叉渐变和同步)。
动画对象组(Animation Object Groups)当需要一组对象接收共享的动画状态时,可以使用AnimationObjectGroup。
支持的格式和加载器(Supported Formats and Loaders)请注意,并非所有模型格式都包含动画(尤其是OBJ,没有), 而且只有某些three.js加载器支持AnimationClip序列。支持此动画类型加载器:THREE.ObjectLoader、THREE.BVHLoader、THREE.ColladaLoader、THREE.FBXLoader、THREE.GLTFLoader、THREE.MMDLoader3ds max和Maya当前无法直接导出多个动画(这意味着动画不是在同一时间线上)到一个文件中。

官方文档范例

let mesh;// 在传入后续代码中需要提前赋值才有意义,可由外部导入的模型数据进行赋值

// 新建一个AnimationMixer, 并取得AnimationClip实例列表
const mixer = new THREE.AnimationMixer( mesh );
const clips = mesh.animations;

// 在每一帧中更新mixer
function update () {
	mixer.update( deltaSeconds );
}

// 播放一个特定的动画
const clip = THREE.AnimationClip.findByName( clips, 'dance' );
const action = mixer.clipAction( clip );
action.play();

// 播放所有动画
clips.forEach( function ( clip ) {
	mixer.clipAction( clip ).play();
} );

实现流程

基本流程

1、添加模型
2、模型动画

工程文件
工程文件结构如下图:
static:存放静态资源文件
three.js-master:为官网下载的代码包,包含所有需要用到的资源包,链接:https://github.com/mrdoob/three.js/archive/master.zipindex.html:页面代码
在这里插入图片描述
模型使用的是官方示例中的Soldier模型,文件位置:three.js-master\examples\models\gltf\Soldier.glb
为了方便操作我们将文件拷出来放在上图static\3dmod\gltf文件夹下,static与three.js-master同级

index.html单页代码组成

<!DOCTYPE html>
<html>

<head>
	<meta charset="utf-8">
	<title>My first three.js app</title>
	<style>
		body {
			margin: 0;
		}
	</style>
</head>

<body>
	<script type="importmap">
			{
				"imports": {
					"three": "./three.js-master/build/three.module.js"
				}
			}
		</script>
	<script type="module">
		// 下文JS代码位置
		// ...
	</script>
</body>

</html>

参照官网例子:https://threejs.org/examples/#webgl_animation_skinning_blending中的场景和模型

场景搭建
搭建场景环境

import * as THREE from "three";
import { OrbitControls } from "./three.js-master/examples/jsm/controls/OrbitControls.js";

let scene, camera, renderer;

// 渲染器开启阴影渲染:renderer.shadowMapEnabled = true;
// 灯光需要开启“引起阴影”:light.castShadow = true;
// 物体需要开启“引起阴影”和“接收阴影”:mesh.castShadow = mesh.receiveShadow = true;

function init() {
	scene = new THREE.Scene();
	camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
	renderer = new THREE.WebGLRenderer();
	// position and point the camera to the center of the scene
	camera.position.set(5, 5, 5);
	camera.lookAt(scene.position);
	
	// 增加坐标系红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
	// 添加坐标系到场景中
	const axes = new THREE.AxesHelper(20);
	scene.add(axes);
	
	// 调整背景颜色,边界雾化
	scene.background = new THREE.Color(0xa0a0a0);
	scene.fog = new THREE.Fog(0xa0a0a0, 10, 30);
	
	// 半球形光源
	const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
	hemiLight.position.set(0, 10, 0);
	scene.add(hemiLight);
	
	// 创建一个虚拟的球形网格 Mesh 的辅助对象来模拟 半球形光源 HemisphereLight.
	const hemiLighthelper = new THREE.HemisphereLightHelper(hemiLight, 5);
	scene.add(hemiLighthelper);
	
	// 地面
	const mesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }));
	mesh.rotation.x = - Math.PI / 2;
	mesh.receiveShadow = true;
	scene.add(mesh);
	
	// 平行光
	const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
	directionalLight.castShadow = true;
	directionalLight.shadow.camera.near = 0.5;
	directionalLight.shadow.camera.far = 50;
	directionalLight.shadow.camera.left = -10;
	directionalLight.shadow.camera.right = 10;
	directionalLight.shadow.camera.top = 10;
	directionalLight.shadow.camera.bottom = -10;
	directionalLight.position.set(0, 5, 5);
	scene.add(directionalLight);
	
	// 用于模拟场景中平行光 DirectionalLight 的辅助对象. 其中包含了表示光位置的平面和表示光方向的线段.
	const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
	scene.add(directionalLightHelper);
	
	renderer.shadowMap.enabled = true;
	renderer.setSize(window.innerWidth, window.innerHeight);
	document.body.appendChild(renderer.domElement);
	
	// 控制器
	const controls = new OrbitControls(camera, renderer.domElement);
}
// 渲染
function animate() {
	requestAnimationFrame(animate);
	renderer.render(scene, camera);
};

添加模型
导入模型,在《Three.js基础入门介绍——Three.js学习四【模型导入】》中有相对详细的介绍。

let model = null;
function loadModel() {
	// 加载模型并开启阴影和接受阴影
	const gltfLoader = new GLTFLoader();
	gltfLoader.setPath('./static/3dmod/gltf/')
		.load('Soldier.glb', function (gltf) {
			gltf.scene.rotation.y = Math.PI;
			// console.log("gltf", gltf)
			gltf.scene.scale.set(1, 1, 1)
			gltf.scene.traverse(function (object) {
				if (object.isMesh) {
					object.castShadow = true; //阴影
					object.receiveShadow = true; //接受别人投的阴影
				}
			});
			scene.add(gltf.scene);
			model = gltf.scene;

		}, function (res) {
			// console.log(res.total, res.loaded)
		});
}

模型动画
动画实现的基本流程

  1. 使用加载器导入模型后,在加载成功后调用的函数中设置动画
  2. 新建一个AnimationMixer(动画混合器)
  3. 获取AnimationClip(动画)实例列表
  4. 设置播放动画,并在每一帧中更新mixer

以文中使用的GLTF加载器(GLTFLoader)为例,其解析基于glTF的ArrayBuffer或JSON字符串,并在完成后触发onLoad回调。onLoad的参数将是一个包含有已加载部分的Object:.scene、 .scenes、 .cameras、 .animations 和 .asset。
其中.scene为AnimationMixer新建时需要传入的内容,.animations 则为AnimationClip(动画)实例列表
在这里插入图片描述

相关对象方法和代码
在设置动画中使用的相关对象及方法:

  • AnimationMixer( rootObject : Object3D ) —— rootObject - 混合器播放的动画所属的对象。

  • AnimationClip( name : String, duration : Number, tracks : Array ) —— name - 此剪辑的名。duration - 持续时间 (单位秒). 如果传入负数, 持续时间将会从传入的数组中计算得到。tracks - 一个由关键帧轨道(KeyframeTracks)组成的数组。

  • AnimationAction( mixer : AnimationMixer, clip : AnimationClip, localRoot : Object3D ) —— mixer - 被此动作控制的 动画混合器。clip - 动画剪辑 保存了此动作当中的动画数据。localRoot - 动作执行的根对象`

  • animationClip.findByName ( objectOrClipArray : Object, name : String ) : AnimationClip ——根据名称搜索动画剪辑(AnimationClip), 接收一个动画剪辑数组或者一个包含名为"animation"的数组的网格(或几何体)作为第一个参数。

  • animationMixer.clipAction (clip : AnimationClip, optionalRoot : Object3D) : AnimationAction ——
    返回所传入的剪辑参数的AnimationAction, 根对象参数可选,默认值为混合器的默认根对象。第一个参数可以是动画剪辑(AnimationClip)对象或者动画剪辑的名称。

  • animationAction.play () : this —— 让混合器激活动作。此方法可链式调用。

animationMixer.update (deltaTimeInSeconds : Number) : this —— 推进混合器时间并更新动画,通常在渲染循环中完成, 传入按照混合器的时间比例(timeScale)缩放过的clock.getDelta

let clock = new THREE.Clock(); // 用于clock.getDelta()
let mixer; 
// 加载器加载完成后添加了动画设置
function loadModel() {
	// 加载模型并开启阴影和接受阴影
	const gltfLoader = new GLTFLoader();
	gltfLoader.setPath('./static/3dmod/gltf/')
		.load('Soldier.glb', function (gltf) {
			gltf.scene.rotation.y = Math.PI;
			console.log("gltf", gltf)
			gltf.scene.scale.set(1, 1, 1)
			gltf.scene.traverse(function (object) {
				if (object.isMesh) {
					object.castShadow = true; //阴影
					object.receiveShadow = true; //接受别人投的阴影
				}
			});

            // 使用动画混合器及配置
			mixer = startAnimation(
				gltf.scene,
				gltf.animations,
				gltf.animations[1].name // animationName,这里是"Run"
			);
			
			scene.add(gltf.scene);
			model = gltf.scene;

		}, function (res) {
			// console.log(res.total, res.loaded)
		});

};

/**
 * 启动特定网格对象的动画。在三维模型的动画数组中按名称查找动画
 * @param skinnedMesh {THREE.SkinnedMesh} 要设置动画的网格
 * @param animations {Array} 数组,包含此模型的所有动画
 * @param animationName {string} 要启动的动画的名称
 * @return {THREE.AnimationMixer} 要在渲染循环中使用的混合器
 */
function startAnimation(skinnedMesh, animations, animationName) {
	const m_mixer = new THREE.AnimationMixer(skinnedMesh);          
	const clip = THREE.AnimationClip.findByName(animations, animationName);
	if (clip) {
		const action = m_mixer.clipAction(clip);
		action.play();
	}
	return m_mixer;
};

function animate() {
	requestAnimationFrame(animate);
	// 更新动画帧
          if(mixer){
              mixer.update(clock.getDelta());
          }
	
	renderer.render(scene, camera);
};

完整代码和实现效果

完整代码

<!DOCTYPE html>
<html>

<head>
	<meta charset="utf-8">
	<title>My first three.js app</title>
	<style>
		body {
			margin: 0;
		}
	</style>
</head>

<body>
	<script type="importmap">
			{
				"imports": {
					"three": "./three.js-master/build/three.module.js"
				}
			}
		</script>
	<script type="module">
		import * as THREE from "three";
		import { OrbitControls } from "./three.js-master/examples/jsm/controls/OrbitControls.js";
		import { GLTFLoader } from "./three.js-master/examples/jsm/loaders/GLTFLoader.js";

		let scene, camera, renderer;
		let model = null; 
        let clock = new THREE.Clock();
        let mixer;

		// 渲染器开启阴影渲染:renderer.shadowMapEnabled = true;
		// 灯光需要开启“引起阴影”:light.castShadow = true;
		// 物体需要开启“引起阴影”和“接收阴影”:mesh.castShadow = mesh.receiveShadow = true;

		function init() {
			scene = new THREE.Scene();
			camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
			renderer = new THREE.WebGLRenderer();
			// position and point the camera to the center of the scene
			camera.position.set(5, 5, 5);
			camera.lookAt(scene.position);

			// 增加坐标系红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
			// 添加坐标系到场景中
			const axes = new THREE.AxesHelper(20);
			scene.add(axes);

			// 调整背景颜色,边界雾化
			scene.background = new THREE.Color(0xa0a0a0);
			scene.fog = new THREE.Fog(0xa0a0a0, 10, 30);

			// 半球形光源
			const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
			hemiLight.position.set(0, 10, 0);
			scene.add(hemiLight);

			// 创建一个虚拟的球形网格 Mesh 的辅助对象来模拟 半球形光源 HemisphereLight.
			const hemiLighthelper = new THREE.HemisphereLightHelper(hemiLight, 5);
			scene.add(hemiLighthelper);

			// 地面
			const mesh = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: 0x999999, depthWrite: false }));
			mesh.rotation.x = - Math.PI / 2;
			mesh.receiveShadow = true;
			scene.add(mesh);

			// 平行光
			const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
			directionalLight.castShadow = true;
			directionalLight.shadow.camera.near = 0.5;
			directionalLight.shadow.camera.far = 50;
			directionalLight.shadow.camera.left = -10;
			directionalLight.shadow.camera.right = 10;
			directionalLight.shadow.camera.top = 10;
			directionalLight.shadow.camera.bottom = -10;
			directionalLight.position.set(0, 5, 5);
			scene.add(directionalLight);

			// 用于模拟场景中平行光 DirectionalLight 的辅助对象. 其中包含了表示光位置的平面和表示光方向的线段.
			const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5);
			scene.add(directionalLightHelper);

			renderer.shadowMap.enabled = true;
			renderer.setSize(window.innerWidth, window.innerHeight);
			document.body.appendChild(renderer.domElement);

			// 控制器
			const controls = new OrbitControls(camera, renderer.domElement);
		};

		function loadModel() {
			// 加载模型并开启阴影和接受阴影
			const gltfLoader = new GLTFLoader();
			gltfLoader.setPath('./static/3dmod/gltf/')
				.load('Soldier.glb', function (gltf) {
					gltf.scene.rotation.y = Math.PI;
					console.log("gltf\ngltf", gltf)
					gltf.scene.scale.set(1, 1, 1)
					gltf.scene.traverse(function (object) {
						if (object.isMesh) {
							object.castShadow = true; //阴影
							object.receiveShadow = true; //接受别人投的阴影
						}
					});

                    // 使用动画混合器及配置
					mixer = startAnimation(
						gltf.scene,
						gltf.animations,
						gltf.animations[1].name // animationName,这里是"Run"
					);
					
					scene.add(gltf.scene);
					model = gltf.scene;

				}, function (res) {
					// console.log(res.total, res.loaded)
				});

		};

		/**
		 * 启动特定网格对象的动画。在三维模型的动画数组中按名称查找动画
		 * @param skinnedMesh {THREE.SkinnedMesh} 要设置动画的网格
		 * @param animations {Array} 数组,包含此模型的所有动画
		 * @param animationName {string} 要启动的动画的名称
		 * @return {THREE.AnimationMixer} 要在渲染循环中使用的混合器
		 */
		function startAnimation(skinnedMesh, animations, animationName) {
			const m_mixer = new THREE.AnimationMixer(skinnedMesh);          
			const clip = THREE.AnimationClip.findByName(animations, animationName);

			if (clip) {
				const action = m_mixer.clipAction(clip);
				action.play();
			}

			return m_mixer;
		};

		function animate() {
			requestAnimationFrame(animate);

			// 更新动画帧
            if(mixer){
                mixer.update(clock.getDelta());
            }
			
			renderer.render(scene, camera);
		};

		init();
		loadModel();
		animate();
	</script>
</body>

</html>

实现效果

在这里插入图片描述

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

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

相关文章

计算机毕业设计-----SSH协会志愿者服务管理系统

项目介绍 该项目分为前后台&#xff0c;分为管理员与普通用户两种角色&#xff0c;前台为普通用户登录&#xff0c;后台为管理员登录&#xff1b; 管理员角色包含以下功能&#xff1a; 管理员登录,管理员管理,志愿者管理,活动管理,捐赠管理,关于我们管理,友情链接管理,新闻类…

Stable Diffusion XL Turbo 文生图和图生图实践

本篇文章聊聊&#xff0c;如何快速上手 Stable Diffusion XL Turbo 模型的文生图和图生图实战。 写在前面 分享一篇去年 11 月测试过模型&#xff0c;为月末分享的文章做一些技术铺垫&#xff0c;以及使用新的环境进行完整复现。 本篇文章相关的代码保存在 soulteary/docker…

Java反转链表,简单算法

文章目录 一、题目二、答案三、我的解决思路总结 Java 单向链表&#xff0c;指的是一种数据结构&#xff0c;用于存储一系列的元素。每个元素包含两部分&#xff1a;一个存储数据的值和一个指向下一个元素的引用。 单向链表由多个节点组成&#xff0c;每个节点都包含一个数据元…

socket网络编程几大模型?看看CHAT是如何回复的?

CHAT回复&#xff1a;网络编程中常见的有以下几种模型&#xff1a; 1. 阻塞I/O模型&#xff08;Blocking I/O&#xff09;&#xff1a;传统的同步I/O模型&#xff0c;一次只处理一个请求。 2. 非阻塞I/O模型&#xff08;Non-blocking I/O&#xff09;&#xff1a;应用程序轮询…

Docker入门安装、镜像与容器下载 —— 基本操作

目录 前言 Docker 1. docker介绍 2. docker安装 3. docker基本使用 3.1 镜像下载 3.2 操作容器 前言 虚拟机&#xff1a;基于主机(物理机或虚机)的多服务实例。在该模式下&#xff0c;软件开发人员可以提供单个或多个物理机或虚机&#xff0c;同时在每个主机上运行多个服…

开发需求总结10-修改el-form-item的label,实现换行并且修改换行字体的样式

需求描述&#xff1a; 目录 需求描述&#xff1a; 相关代码&#xff1a; 额外拓展&#xff1a; 在form表单上&#xff0c;有个别label可能需要在下方有红色小字用来提示&#xff0c;这条数据的注意点&#xff0c;此时就需要实现label可以换行&#xff0c;并且给下面的小字设置…

【学习笔记】2、逻辑代数与硬件描述语言基础

2.1 逻辑代数 &#xff08;1&#xff09;逻辑代数的基本定律和恒等式 基本定律或 “”与 “”非 “—”0-1律A0AA11AAAA A ‾ \overline{A} A1(互补律)A00A1AAAAA A ‾ \overline{A} A0 A ‾ ‾ \overline{\overline{A}} AA结合律(AB)C A(BC)(AB)CA(BC)ABC交换律AB BAABBA分…

exFAT文件系统识别不了怎么办?

一般存储驱动器通常会使用几种文件系统&#xff0c;其中比较常见的是FAT32、NTFS和exFAT&#xff0c;那么它们之间有什么区别呢&#xff1f;exFAT文件系统识别不了怎么办&#xff1f; 常用文件系统之间的区别有哪些&#xff1f; FAT32文件系统&#xff1a;它是一个兼容性非常强…

知识分享:一文读懂AIGC与大模型

什么是大模型&#xff1f; 关于大模型&#xff0c;有学者称之为“大规模预训练模型”(large pretrained language model&#xff09;&#xff0c;也有学者进一步提出”基础模型”(Foundation Models)的概念。 “小模型”&#xff1a;针对特定应用场景需求进行训练&a…

快快销ShopMatrix 分销商城多端uniapp可编译5端 - 佣金倍数提现

本文来自应用中心-9999款应用在线选购 "佣金倍数提现"这个概念在不同的上下文中可能有不同的含义&#xff0c;但通常它涉及到基于用户赚取的佣金来设定提现条件。这是一种常见的机制&#xff0c;尤其是在那些提供佣金或回扣的平台上&#xff0c;如联盟营销、金融交易…

武汉灰京文化:抓住用户心理,游戏推广不可或缺的前提

在当今激烈竞争的游戏市场中&#xff0c;了解目标用户成为游戏推广的不可或缺的前提。不同类型的游戏适合不同的用户群体&#xff0c;因此通过深入研究用户画像&#xff0c;准确定位目标用户群体&#xff0c;成为游戏成功推广的关键一环。游戏推广不仅仅是让更多的人知道游戏的…

三方接口对接常见数据处理方式汇总

文章目录 数据请求格式application/json接收发送 multipart/form-data接收发送 application/x-www-form-urlencoded接收发送 text/xml接收发送 Request请求中各个数据载体获取方法HeaderParameterInputStreamAttribute 二次封装HttpServletRequest参考 验签与加密 日常开发中&a…

运筹说 第90期 | 网络计划-图解评审法

前述章节的网络计划方法主要研究以时间为主要参数的确定型网络模型&#xff0c;其中的概率型网络模型也只讨论工作公式的不确定性&#xff0c;并没有对事项或工作的不确定性进行讨论。由于这类网络模型的建立有严格的规则&#xff0c;大量研究与开发类计划尚无法表达。因从本期…

【51单片机系列】单片机与PC进行串行通信

一、单片机与PC机串行通信的设计 工业现场的测控系统中&#xff0c;常使用单片机进行监测点的数据采集&#xff0c;然后单片机通过串口与PC通信&#xff0c;把采集的数据串行传送到PC机上&#xff0c;再在PC机上进行数据处理。 PC机配置的都是RS-232标准串口&#xff0c;为D型…

steam游戏搬砖项目还能火多久?

最近放假回到老家&#xff0c;见了不少亲戚朋友&#xff0c;大家不约而同都在感叹今年大环境不好&#xff0c;工作不顺&#xff0c;生意效益不好&#xff0c;公司状况不佳&#xff0c;反问我们生意如何&#xff1f;为了让他们心里好受一点&#xff0c;我也假装附和道:也不咋地&…

【项目经验】详解Puppeteer入门及案例

文章目录 一.项目需求及Puppeteer是什么&#xff1f;二.Puppeteer注意事项及常用的方法1.注意事项2.常用的方法*puppeteer.launch&#xff08;&#xff09;**browser.newPage()**page.goto()**page.on(request&#xff0c;&#xff08;&#xff09;> {}&#xff09;**page.e…

如何根据水利需求选择合适的遥测终端机

在水利信息化建设中&#xff0c;遥测终端机作为关键的设备之一&#xff0c;发挥着越来越重要的作用。如何根据实际的水利需求选择合适的遥测终端机&#xff0c;成为了众多企业和单位关注的焦点。本文将为您揭示遥测终端机的选择之道&#xff0c;助您在水利信息化建设中取得事半…

微信接入知识库定制化的AI会怎样?

想不想要一个更加了解你的chatgpt&#xff1f;或者想给chatgpt加入特定的知识库&#xff1f; LinkAI来帮你&#xff01; 通过LinkAI&#xff0c;无需openai的api key&#xff0c;直接使用chatgpt。无需考虑服务器代理配置&#xff0c;openai账号注册等&#xff01;自定义知识…

3.3.3 使用集线器的星形拓扑

3.3.3 使用集线器的星形拓扑 集线器的一些特点 3.3.4 以太网的信道利用率 多个站在以太网上同时工作就可能会发生碰撞当发生碰撞时&#xff0c;信道资源实际上是被浪费了。因此&#xff0c;当扣除碰撞所造成的信道损失后&#xff0c;以太网总的信道利用率并不能达到100% 3.…

多个table的选中问题

多个table的选中问题 场景&#xff1a;循环出来多个table&#xff0c;最后拿到所有选中的数据 出现的问题&#xff1a;比如先选择第一个table的某些数据&#xff0c;再去选另外的table&#xff0c;这样selection里面只有最后选中的table的数据。 解决方法&#xff1a;在sele…