ORCA-3D避障代码和原理解析

news2024/11/15 18:47:23

二维ORCA原理参考:
https://zhuanlan.zhihu.com/p/669426124

ORCA原理图解+代码解释

1. 找到避障速度增量 u

碰撞处理分为三种情况:
(1)没有发生碰撞,且相对速度落在小圆里
(2)没有发生碰撞,且相对速度落在圆锥里
(3)发生碰撞,马上做出反应
请添加图片描述
timeStep 决定了仿真每一步的时间更新间隔,是系统的时间推进基础。较小的 timeStep 可以提高仿真的精度,但会增加计算量。

timeHorizon 决定了智能体在进行避障计算时预测的时间范围。较大的 timeHorizon 值使得智能体可以更早预测潜在碰撞,但会减少它的速度选择自由度。

timeStep 是碰撞时需要计算的调整u所需的时间
timeHorizon 是未发生碰撞时,需要计算的u所化的时间,他是一种提前预测

2. 添加速度障碍平面

表示一个平面需要法向量和平面上的点
请添加图片描述

1和2对应代码如下

	// 它使用ORCA(Optimal Reciprocal Collision Avoidance)方法来计算智能体之间的避碰行为
	void Agent::computeNewVelocity()
	{
		orcaPlanes_.clear();				  // 清空ORCA平面列表
		const float invTimeHorizon = 1.0f / timeHorizon_; // 计算时间视野的倒数

		/* 创建智能体的ORCA平面 */
		for (size_t i = 0; i < agentNeighbors_.size(); ++i)
		{								       // 遍历每个邻居智能体
			const Agent *const other = agentNeighbors_[i].second;	       // 获取邻居智能体指针
			//这里的position_是在rvo->updateState添加的当前agent的位置
			// 改这块就好了===============================
			const Vector3 relativePosition = other->position_ - position_; // 计算相对位置
			const Vector3 relativeVelocity = velocity_ - other->velocity_; // 计算相对速度
			// const Vector3 relativePosition = relative_position_; // 计算相对位置
			// const Vector3 relativeVelocity = relative_velocity_; // 计算相对速度

			const float distSq = absSq(relativePosition);		       // 计算相对位置的平方距离
			const float combinedRadius = radius_ + other->radius_;	       // 计算合并半径
			const float combinedRadiusSq = sqr(combinedRadius);	       // 计算合并半径的平方

			Plane plane; // 定义一个平面对象
			Vector3 u;   // 定义速度调整向量

			if (distSq > combinedRadiusSq)
			{										// 如果没有发生碰撞
				// w表示给定时间视野TimeHorizon内,两个智能题之间的相对速度偏移量
				const Vector3 w = relativeVelocity - invTimeHorizon * relativePosition; // 计算从截断中心到相对速度的向量
				const float wLengthSq = absSq(w);					// 计算w向量的平方长度

				const float dotProduct = w * relativePosition; // 计算w向量和相对位置的点积
				
				// 1. 如果投影在截断圆上
				// dotProduct表示相差的速度和相差的位置的点乘,要是点乘小于0,表示在靠近
				if (dotProduct < 0.0f && sqr(dotProduct) > combinedRadiusSq * wLengthSq)
				{						    
					const float wLength = std::sqrt(wLengthSq); // 计算w向量的长度
					const Vector3 unitW = w / wLength;	    // 计算w向量的单位向量

					plane.normal = unitW;					 // 设置平面的法向量
					u = (combinedRadius * invTimeHorizon - wLength) * unitW; // 计算速度调整向量
				}
				// 2. 如果投影在圆锥上
				else
				{																  
					const float a = distSq;													  // 设置系数a
					const float b = relativePosition * relativeVelocity;									  // 设置系数b
					const float c = absSq(relativeVelocity) - absSq(cross(relativePosition, relativeVelocity)) / (distSq - combinedRadiusSq); // 设置系数c
					// t表示圆锥中心线到斜线的距离 对于 半径的倍数
					const float t = (b + std::sqrt(sqr(b) - a * c)) / a;									  // 计算t值
					const Vector3 w = relativeVelocity - t * relativePosition;								  // 计算w向量
					const float wLength = abs(w);												  // 计算w向量的长度
					const Vector3 unitW = w / wLength;											  // 计算w向量的单位向量

					plane.normal = unitW;			    // 设置平面的法向量
					u = (combinedRadius * t - wLength) * unitW; // 计算速度调整向量
				}
			}
			// 3. 如果发生碰撞
			else
			{									     
				const float invTimeStep = 1.0f / sim_->timeStep_;		     // 计算时间步长的倒数
				const Vector3 w = relativeVelocity - invTimeStep * relativePosition; // 计算w向量
				const float wLength = abs(w);					     // 计算w向量的长度
				const Vector3 unitW = w / wLength;				     // 计算w向量的单位向量

				plane.normal = unitW;				      // 设置平面的法向量
				u = (combinedRadius * invTimeStep - wLength) * unitW; // 计算速度调整向量
			}
			
			// 有多少个neighbor,就有多少个orca平面
			plane.point = velocity_ + 0.5f * u; // 计算平面上的点
			orcaPlanes_.push_back(plane);	    // 将平面添加到ORCA平面列表中
		}

		const size_t planeFail = linearProgram3(orcaPlanes_, maxSpeed_, prefVelocity_, false, newVelocity_); // 计算新的速度,如果失败返回失败的平面索引

		if (planeFail < orcaPlanes_.size())
		{									 // 如果存在失败的平面
			linearProgram4(orcaPlanes_, planeFail, maxSpeed_, newVelocity_); // 调用备用算法处理失败的平面
		}
	}

3. 线性规划求解出最优速度

linearProgram几个函数实现了一套线性规划(Linear Programming, LP)求解方法,目的是在有多个平面约束(即避障条件)的情况下找到最优的速度向量,以确保多个智能体不会发生碰撞。

初始调用

// 调用 linearProgram3 来计算满足所有约束(平面)的新的速度向量。如果失败,则返回失败的平面索引。
	const size_t planeFail = linearProgram3(orcaPlanes_, maxSpeed_, prefVelocity_, false, newVelocity_); // 计算新的速度,如果失败返回失败的平面索引

	if (planeFail < orcaPlanes_.size()) // 如果存在失败的平面
	{									 
		// 调用备用算法处理失败的平面
		linearProgram4(orcaPlanes_, planeFail, maxSpeed_, newVelocity_); 
	}
3.1 linearProgram3():求解所有平面的初步速度
	size_t linearProgram3(const std::vector<Plane> &planes, float radius, const Vector3 &optVelocity, bool directionOpt, Vector3 &result)
	{
		if(directionOpt) /* 如果使用方向优化,即只考虑速度方向,而不考虑速度大小。*/
		{
			result = optVelocity * radius; // 如果优化方向,将最优速度扩展到给定的半径
		}
		else if (absSq(optVelocity) > sqr(radius))
		{
			/* 优化最近点并且在圆外。 ?? 是不是为了安全性啊,本来optVelocity不就是单位向量了吗*/
			result = normalize(optVelocity) * radius; // 如果最优速度的平方长度大于半径的平方,将其归一化并扩展到给定的半径
		}
		else
		{  /* 优化最近点并且在圆内。 */
			result = optVelocity; // 如果最优速度的平方长度小于或等于半径的平方,直接使用最优速度
		}

		for (size_t i = 0; i < planes.size(); ++i)
		{
			// result 位于平面的外侧,不满足orca约束
			if (planes[i].normal * (planes[i].point - result) > 0.0f)
			{
				const Vector3 tempResult = result; // 保存当前结果
				if (!linearProgram2(planes, i, radius, optVelocity, directionOpt, result))
				{
					result = tempResult; // 如果 linearProgram2 返回 false,恢复之前的结果
					return i;	     // 返回不满足的平面的索引
				}
			}
		}
		return planes.size(); // 如果所有约束都满足,返回 planes.size()
	}
3.2 linearProgram2():解决单个平面约束的最优速度

如图求出初步速度之后,这是满足了planeNo的约束,但可能破坏之前平面的约束,因此需要遍历planeNo之前的平面做检查
在这里插入图片描述

// 用于计算满足给定约束的速度向量。这个函数的主要目的是在智能体的避碰算法中,在一个半径为radius的球体内找到一个速度向量,使其尽可能接近给定的最优速度optVelocity,同时满足所有给定的平面约束。
	bool linearProgram2(const std::vector<Plane> &planes, size_t planeNo, float radius, const Vector3 &optVelocity, bool directionOpt, Vector3 &result)
	{
		const float planeDist = planes[planeNo].point * planes[planeNo].normal; // 计算平面与原点的距离
		const float planeDistSq = sqr(planeDist);				// 计算距离的平方
		const float radiusSq = sqr(radius);					// 计算半径的平方
		
		// 用超过最大速度的速度才能达到orca避障平面
		if (planeDistSq > radiusSq)
		{
			/* 最大速度球完全使平面 planeNo 无效。 */
			return false; // 如果平面距离的平方大于半径的平方,则返回false,表示无解
		}

		// 勾股定理计算最大速度radiusSq与平面中线的另外一边的平方
		const float planeRadiusSq = radiusSq - planeDistSq;		
		// 计算原点到平面中心的线
		const Vector3 planeCenter = planeDist * planes[planeNo].normal; 

		if (directionOpt)
		{
			/* 投影方向 optVelocity 到平面 planeNo 上。 */
			// optVelocity * planes[planeNo].normal 表示 optVelocity 在 planes[planeNo].normal 方向上的投影长度,这是一个标量,再乘以法向量,使之变为矢量
			const Vector3 planeOptVelocity = optVelocity - (optVelocity * planes[planeNo].normal) * planes[planeNo].normal; // 计算平面上的最优速度
			const float planeOptVelocityLengthSq = absSq(planeOptVelocity);							// 计算平面上最优速度的平方长度

			if (planeOptVelocityLengthSq <= RVO_EPSILON)
			{
				result = planeCenter; // 如果最优速度的平方长度小于一个很小的值,则结果为平面中心
			}
			else
			{
				// 否则,计算结果为平面中心加上最优速度在平面上的投影
				result = planeCenter + std::sqrt(planeRadiusSq / planeOptVelocityLengthSq) * planeOptVelocity; 
			}
		}
		else
		{
			// 结果是optVelocity + optVelocity顶点离平面的最小距离向量
			result = optVelocity + ((planes[planeNo].point - optVelocity) * planes[planeNo].normal) * planes[planeNo].normal; // 计算点在平面上的投影

			// 就是结果超过最大速度
			if (absSq(result) > radiusSq)
			{
				const Vector3 planeResult = result - planeCenter;				     // 计算结果相对于平面中心的向量
				const float planeResultLengthSq = absSq(planeResult);				     // 计算该向量的平方长度
				// 结果就是最大圆与平面的交点,并且里原始方向近的那个交点形成的向量
				result = planeCenter + std::sqrt(planeRadiusSq / planeResultLengthSq) * planeResult; 
			}
		}

		// 新的result被求出,满足了planeNo的约束,但可能破坏之前的约束,因此需要检查
		for (size_t i = 0; i < planeNo; ++i)
		{
			if (planes[i].normal * (planes[i].point - result) > 0.0f)
			{	
				// 计算两个平面的法向量的叉积
				Vector3 crossProduct = cross(planes[i].normal, planes[planeNo].normal); 

				// 平面 planeNo 和 i(几乎)平行,因为平面 i 失败了,所以之前求出的平面 planeNo 也失败了
				if (absSq(crossProduct) <= RVO_EPSILON)
				{
					
					return false; // 返回false
				}

				// 算出交线,result指到交线,那就可以同时满足这两个平面约束了呀
				Line line;
				line.direction = normalize(crossProduct); //平面交线方向		
				// 平面planeNo上并垂直于交线的线									      
				const Vector3 lineNormal = cross(line.direction, planes[planeNo].normal);	
				// ((planes[i].point - planes[planeNo].point) * planes[i].normal)两平面点连线在平面i法向量上的投影								      
				line.point = planes[planeNo].point + (((planes[i].point - planes[planeNo].point) * planes[i].normal) / (lineNormal * planes[i].normal)) * lineNormal; 

				if (!linearProgram1(planes, i, line, radius, optVelocity, directionOpt, result))
				{
					return false; // 如果 linearProgram1 返回 false,表示无解,返回 false
				}
			}
		}

		return true; // 返回 true,表示找到了解
	}

3.3 linearProgram1():寻找线与圆形区域的交点

在这里插入图片描述

// 用于在一个圆形区域内(以给定的半径为界限)找到一条线的交点。
	bool linearProgram1(const std::vector<Plane> &planes, size_t planeNo, const Line &line, float radius, const Vector3 &optVelocity, bool directionOpt, Vector3 &result)
	{
		const float dotProduct = line.point * line.direction;			      // 计算点和方向的点积
		const float discriminant = sqr(dotProduct) + sqr(radius) - absSq(line.point); // 计算判别式,用于判断交点是否在圆形区域内

		if (discriminant < 0.0f)
		{
			// 如果判别式小于0,表示没有交点,返回false
			return false; 
		}

		const float sqrtDiscriminant = std::sqrt(discriminant); // 计算判别式的平方根
		float tLeft = -dotProduct - sqrtDiscriminant;		// 计算t的左边界
		float tRight = -dotProduct + sqrtDiscriminant;		// 计算t的右边界

		for (size_t i = 0; i < planeNo; ++i)
		{
			const float numerator = (planes[i].point - line.point) * planes[i].normal; // 计算分子
			const float denominator = line.direction * planes[i].normal;		   // 计算分母

			if (sqr(denominator) <= RVO_EPSILON)
			{
				/* 线几乎与平面i平行。 */
				if (numerator > 0.0f)
				{
					return false; // 如果分子大于0,返回false,表示无解
				}
				else
				{
					continue; // 否则继续下一个平面
				}
			}

			const float t = numerator / denominator; // 计算t值

			if (denominator >= 0.0f)
			{
				/* 平面i限制线的左边界。 */
				tLeft = std::max(tLeft, t); // 更新t的左边界
			}
			else
			{
				/* 平面i限制线的右边界。 */
				tRight = std::min(tRight, t); // 更新t的右边界
			}

			if (tLeft > tRight)
			{
				return false; // 如果左边界超过右边界,返回false,表示无解
			}
		}

		// 优化方向
		if (directionOpt)
		{
			if (optVelocity * line.direction > 0.0f)
			{
				// 如果方向优化,则选择tRight作为结果
				result = line.point + tRight * line.direction; 
			}
			else
			{
				// 否则选择tLeft作为结果
				result = line.point + tLeft * line.direction; 
			}
		}
		else
		{
			/* 优化最近点。 */
			const float t = line.direction * (optVelocity - line.point); // 计算最优t值

			if (t < tLeft)
			{
				result = line.point + tLeft * line.direction; // 如果t小于左边界,选择tLeft作为结果
			}
			else if (t > tRight)
			{
				result = line.point + tRight * line.direction; // 如果t大于右边界,选择tRight作为结果
			}
			else
			{
				result = line.point + t * line.direction; // 否则选择t作为结果
			}
		}

		return true; // 返回true,表示找到了解
	}

3.4linearProgram4():处理多个平面之间的约束冲突

其实这个代码是在构造一个新的平面集合projPlanes,然后重新调用linearProgram3()求解

projPlanes是对从有冲突的平面开始拿出一个平面i,然后找到这个平面i之前的平面j,用这两个平面i和平面j构造出一个中间平面重新调用linearProgram3()来解决问题

所谓的中间平面就是:

  • 当两平面平行,就是中间的平行平面
  • 当两平面相交,就是夹角那个方向的平面

其实我不是很理解这个中间平面的构造,但可以大致想一下就是因为两个平面ij限制太多了才找不到解,反正解也都是在平面边缘处找到的,不如找一个折中的平面,尝试一下这个位置是不是能找到。。。(待定)

	// 当 linearProgram3 从beginPlane无法满足约束时,linearProgram4 进一步处理这些情况。
	void linearProgram4(const std::vector<Plane> &planes, size_t beginPlane, float radius, Vector3 &result)
	{
		float distance = 0.0f; // 初始化距离为0

		for (size_t i = beginPlane; i < planes.size(); ++i)
		{
			if (planes[i].normal * (planes[i].point - result) > distance)
			{
				std::vector<Plane> projPlanes; 

				for (size_t j = 0; j < i; ++j)
				{
					Plane plane;
					// 计算两个平面的法向量的叉积,可以表示两个平面的交线的方向
					const Vector3 crossProduct = cross(planes[j].normal, planes[i].normal); 
					
					// 利用叉乘判断平面是否平行,绝对值小于0.1,则平行
					if (absSq(crossProduct) <= RVO_EPSILON) //RVO_EPSILON=0.1
					{	
						// 利用点乘判断平行平面方向,平面 i 和平面 j 指向相同方向
						if (planes[i].normal * planes[j].normal > 0.0f)
						{
							
							continue;
						}
						else // 平面 i 和平面 j 指向相反方向。
						{
							// 平面点是两个平面点的中点
							plane.point = 0.5f * (planes[i].point + planes[j].point); 
						}
					}
					else
					{
						// 在平面 i 内部,且垂直于交线方向的向量
						const Vector3 lineNormal = cross(crossProduct, planes[i].normal);	
						// (planes[j].point - planes[i].point) * planes[j].normal 是两点连线在平面 j 法向量方向上的投影			
						// (lineNormal * planes[j].normal) 表示 lineNormal 在平面 j 法向量方向上的投影。
						plane.point = planes[i].point + (((planes[j].point - planes[i].point) * planes[j].normal) / (lineNormal * planes[j].normal)) * lineNormal; // 计算交点
					}

					plane.normal = normalize(planes[j].normal - planes[i].normal); // 计算投影平面的法向量并归一化
					projPlanes.push_back(plane);				       // 将投影平面添加到列表中
				}

				const Vector3 tempResult = result; // 保存当前结果

				if (linearProgram3(projPlanes, radius, planes[i].normal, true, result) < projPlanes.size())
				{
					/* 原则上不应该发生。这是因为结果已经在这个线性规划的可行区域内。如果失败,是由于小的浮点误差,并保持当前结果。 */
					result = tempResult;
				}

				distance = planes[i].normal * (planes[i].point - result); // 更新距离
			}
		}
	}

}

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

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

相关文章

CANopen从站为什么总不上传PDO报文?

在CANopen网络中无法获取从站的TPDO数据&#xff1f;本文将为您解析可能的原因及解决方案。通过检查TPDO的通信参数和传输类型&#xff0c;确保主站与从站的数据同步&#xff0c;快速定位问题所在。 如果你的CANopen网络中已经确保接线和波特率都没有问题&#xff0c;但无论主站…

在线教程丨1 步生成 SOTA 级别图像,Hyper-SD 一键启动教程上线!

近年来&#xff0c;扩散模型在文生图任务中得到了广泛的应用&#xff0c;但其在实现高质量图像生成的过程中&#xff0c;通常需要多步推理进行去噪&#xff0c;这显然大大增加了计算资源成本。 针对于此&#xff0c;研究人员引入蒸馏算法&#xff0c;推出了扩撒感知蒸馏算法来…

图结构的稀疏变换器:EXPHORMER框架

人工智能咨询培训老师叶梓 转载标明出处 尽管图变换器在理论上具有强大的表达能力&#xff0c;但是它们在扩展到大型图时面临着巨大的挑战。这一挑战主要源于其全局注意力机制的二次方时间复杂度&#xff0c;这不仅限制了其在大型图数据集上的应用&#xff0c;也使得其在内存和…

超实用的分数查询系统,老师不可错过!

在学校与家庭的互动中&#xff0c;成绩往往像一颗不定时炸弹。我们都知道&#xff0c;每次考试成绩公布后&#xff0c;就像一场风暴即将席卷平静的港湾。 有这样一个案例&#xff0c;一位老师辛苦地批改完试卷&#xff0c;将成绩以传统的表格形式发在班级群里。这一下可捅了马蜂…

【mysql技术内幕】

MySQL之技术内幕 1.MVCC模式2. 实现mvcc模式的基础点3.MySQL锁的类型4. 谈谈分库分表5. 分表后的id咋么保证唯一性呢&#xff1f;6. 分表后非sharding key的查询咋么处理的&#xff1f; 1.MVCC模式 MVCC, 是multi-version concurrency control的缩写&#xff0c;即多版本并发控…

【时时三省】(C语言基础)指针笔试题1

山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 笔试题1: 创建了一个a数组 它有五个元素 五个元素分别是1 2 3 4 5 &a取出来的是一维数组的地址 然后产生的结果强制类型转换了成int &a+1就是从1跳到了5 如下图 再把这个地…

Java开发-面试题-0035-Spring代理方式有哪些

Java开发-面试题-0035-Spring代理方式有哪些 更多内容欢迎关注我&#xff08;持续更新中&#xff0c;欢迎Star✨&#xff09; Github&#xff1a;CodeZeng1998/Java-Developer-Work-Note &#xff08;技术&#xff09;微信公众号&#xff1a;CodeZeng1998 &#xff08;生活&…

品牌力是什么?如何评估企业品牌影响力?

品牌影响力&#xff0c;其实就是指品牌在消费者心智中所占据的位置&#xff0c;以及它对消费者购买决策和行为的影响力。如果一个企业的品牌影响力越强&#xff0c;它在消费者心中的印象就越深刻&#xff0c;能够更有效地驱动消费者的购买行为&#xff0c;形成品牌忠诚度&#…

SpringCloud (1) 服务拆解

1 服务拆解和治理 1.1 服务拆解 微服务的核心就是服务拆分,将传统的大项目拆分为多个微型服务(服务或微服务),实现服务之间"高内聚(微服务职责单一),低耦合(微服务功能相对独立)"的目的 (1) 水平(横向)拆分:先搭出拆分框架,比如【公共服务】(比如:common服务,client…

数字工厂管理系统与MES系统在实际应用中有哪些区别

随着制造业的数字化转型步伐加快&#xff0c;数字工厂管理系统与制造执行MES系统作为两大关键工具&#xff0c;在实际应用中展现出了明显的差异。本文将从实际应用的角度&#xff0c;详细探讨这两种系统之间的主要区别。 数字工厂管理系统的实际应用 数字工厂管理系统侧重于对…

Java基础(中)

面向对象基础 面向对象和面向过程的区别 面向过程编程&#xff08;Procedural-Oriented Programming&#xff0c;POP&#xff09;和面向对象编程&#xff08;Object-Oriented Programming&#xff0c;OOP&#xff09;是两种常见的编程范式&#xff0c;两者的主要区别在于解决…

monaco editor 在react中的使用

1. 首先先导入monaco editor npm install monaco-editor// npm install monaco-editor --force // 版本冲突? 强行安装 2. 在react中使用 期望效果如下 3. 我遇到的问题 : 输入json数据后 未格式化 , json数据仍然一行展示 我遇到的问题 : 直接输入json数据会白屏报错…

OSSEC搭建与环境配置Ubuntu

尝试使用Ubuntu配置了OSSEC&#xff0c;碰见很多问题并解决了&#xff0c;发表博客让后来者不要踩那么多坑 环境 &#xff1a; server &#xff1a;Ubuntu22.04 64位 内存4GB 处理器4 硬盘60G agent: 1.Windows11 64位 2.Ubuntu22.04 64位 服务端配置 一、配置安装依赖项&…

解决Python模块导入报错的问题

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 模块导入 📒📝 实际案例分享📝 解决方案📝 导入包的技巧和常见问题1. 导入包的技巧2. 常见问题及注意事项⚓️ 相关链接 ⚓️📖 介绍 📖 今天写Python代码的时候,遇到了一个模块导入报错的情况,这个问题不仅困扰了…

CDGA|怎样的数据治理状态才能被视为是良性发展的呢?

在当今这个数据驱动的时代&#xff0c;数据已成为企业最宝贵的资产之一&#xff0c;其质量、安全性和有效利用程度直接关系到企业的竞争力与可持续发展。因此&#xff0c;构建并维持一个良性的数据治理状态&#xff0c;对于企业而言至关重要。那么&#xff0c;怎样的数据治理状…

Linux中使用Docker容器构建Tomcat容器完整教程

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;Linux高级管理防护和群集专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️创作…

高效分数查询系统助力管理班级

老师们的工作现在可太忙啦&#xff01;每天要做的事儿那叫一个繁杂。就说备课吧&#xff0c;得翻好多书&#xff0c;参考不同的教材&#xff0c;还得考虑每个学生的学习情况&#xff0c;想办法让课讲得有意思又能让学生学到东西。 从上课一开始怎么吸引学生&#xff0c;到中间每…

智慧交通,智能消防系统助力高铁站安全

智慧交通是一项基于现代技术的创新领域&#xff0c;正不断为我们生活带来便利。在智慧交通领域中&#xff0c;高铁站是一个非常重要的环节。高铁站作为人流密集的区域&#xff0c;安全问题一直备受关注。为了提升高铁站的安全性和效率&#xff0c;智慧消防设备监测与集中监控系…

20240919 - 【PYTHON】辞职信

import tkinter as tk # 导入 tkinter 模块&#xff0c;并简写为 tk from tkinter import messagebox # 从 tkinter 导入 messagebox 子模块&#xff0c;用于显示消息框 from random import random # 从 random 模块导入 random 函数&#xff0c;用于生成随机数# 创建窗口对…

ai写作软件排行榜前十名,5个软件帮助你快速使用ai写作

ai写作软件排行榜前十名&#xff0c;5个软件帮助你快速使用ai写作 AI写作软件已经成为许多人工作和创作中的重要工具&#xff0c;尤其是在快速生成内容、提高写作效率以及优化文本方面。以下是五款优秀的AI写作软件&#xff0c;它们能够帮助你轻松完成各种写作任务&#xff0c…