基于QT使用OpenGL,加载obj模型,进行鼠标交互
目录
- 功能分析(需求分析)
- 技术点分析
- OpenGL
- 立即渲染模式
- 可编程渲染管线模式
- QOpenGLWidget
- 派生类 glwidget逻辑
- glwidget.h
- glwidget.cpp
- 鼠标交互功能
- obj格式介绍
- 效果
- bunny
- Cayman_GT
功能分析(需求分析)
- 基于QT平台,使用OpenGL进行obj文件加载显示;
- 使用鼠标对场景进行缩放、移动、旋转交互;
技术点分析
OpenGL
OpenGL是基于C的,学习曲线比较抖,但是总的来说就是下面一幅图,
用语言简单的描述(个人理解,可能不太准确)是把cpu里内存里的3D数据,传输到显卡的内存里,以及如何转换成2D平面上像素点显示(也就是矩阵变换,在好多开源的框架里都进行了进一步封装,形成了渲染器、场景、相机等)。这个数据是顶点坐标、颜色等,传输跟送快递一样,除了要有数据本身之外,还要有其他信息,也就是要有个约定,到显卡拿到数据之后怎么解析。
有个网站比较有名learnopengl,可以对着学一遍,在网上找资料的时候,需要注意是立即渲染模式,还是可编程渲染管线模式,现在官方推荐是使用可编程渲染管线模式开发。
立即渲染模式
glBegin(GL_TRIANGLES)
glTranslatef(1,2,3);
// 其他操作
// glVertex*() 设置顶点坐标
// glColor*() 设置当前颜色
// glIndex*() 设置当前颜色表
// glNormal*() 设置法向坐标
// glEvalCoord*() 产生坐标
// glTexCoord*() 设置纹理坐标
// glEdgeFlag*() 控制边界绘制
// glMaterial*() 设置材质
glEnd()
可编程渲染管线模式
1、创建VBO顶点数据对象GLuint VBO;glGenBuffers(1,&VBO);
2、VBO与显卡缓存绑定glBindBuffer(GL_ARRAY_BUFFER, VBO)
3、绑定数据缓存对象glBufferData(GL_ARRAY_BUFFER,sizeof(vertexs),vertex,)
4、数据格式glVertexAttribPoint(0,3,GL_FLOAT,)
5、启用/绘制glEnableVertexAttribArray(0)glDrawArrays(GL_TRIANGLES, 0, 10);
6、最后关闭glBindBuffer(GL_ARRAY_BUFFER,0)glBindVertexArray(0);
以上两段代码只是展示两种模式区别,很明显立即渲染模式容易理解,但是性能有限制,可编程渲染管线模式理解和使用门槛高,但更能接触底层。
QOpenGLWidget
QT对OpenGL进行了封装,提供了QOpenGLWidget类,只需要对其继承,在initialzeGL(),resizeGL(),paintGL()逻辑下进行业务实现,包括VBO、VAO的创建与绑定,着色器程序的编译与链接等。
派生类 glwidget逻辑
先简单介绍派生类的写法,当然为了方便扩展和使用,抽象出了相机类、渲染器类、物体类,完整的工程代码资源可以下载参考,具体的可以根据自己要实现的功能进行编写。
glwidget.h
#ifndef GLWIDGET_H
#define GLWIDGET_H#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>
#include <QOpenGLVertexArrayObject>
#include <QMouseEvent>
#include <QTimer>
#include <QQuaternion>#include <vector>
#include <QKeyEvent>#include "utils/Common.h"
#include "genericRender.h"
#include "grid.h"
#include "coorsystem.h"
#include "camera.h"class glwidget :public QOpenGLWidget, QOpenGLFunctions_3_3_Core
{Q_OBJECT
public:glwidget(QWidget * parent = nullptr);//鼠标交互事件重写void mouseMoveEvent(QMouseEvent *event) override;void mousePressEvent(QMouseEvent *event) override;void mouseReleaseEvent(QMouseEvent *event) override;void wheelEvent(QWheelEvent *event) override;//键盘交互void keyReleaseEvent(QKeyEvent *event) override; //按键释放事件void keyPressEvent(QKeyEvent *event) override; //按键按下事件void slotTimeOut();
protected:virtual void initializeGL();virtual void resizeGL(int w, int h);virtual void paintGL();
public:GenericRender m_render;//渲染器Camera m_camera;//相机grid m_grid;//网格平面xycoorSystem m_coordsys;//坐标系QTimer tm_;
public:QList<int> keys;QTimer* keyRespondTimer; //头文件中添加成员static int mouse_button;static int modifier_key;
public://鼠标变量static double mouse_pos_x_old;static double mouse_pos_y_old;bool changeview_ = false; //改变视角的标志位float fov = 2.0f; //视野范围
};
#endif // GLWIDGET_H
glwidget.cpp
#include "glwidget.h"
#include "box.h"
int glwidget::mouse_button = -1;
int glwidget::modifier_key = 0;
double glwidget::mouse_pos_x_old = 0;
double glwidget::mouse_pos_y_old = 0;
glwidget::glwidget(QWidget * parent):QOpenGLWidget(parent)
{keyRespondTimer = new QTimer(this); //构造函数中创建定时器对象,并连接信号槽connect(keyRespondTimer, &QTimer::timeout, this, &glwidget::slotTimeOut);setFocusPolicy(Qt::StrongFocus);//否则进入不了键盘事件监听
}void glwidget::initializeGL()
{initializeOpenGLFunctions();glEnable(GL_DEPTH_TEST);m_render.init();m_camera.setMatPro(fov);QStringList renderfiles={":/obj/data/Cayman_GT.obj", ":/obj/data/bunny_10k.obj"};for(QString file : renderfiles){Box* b1 = new Box();b1->load(file);m_render.addBox(b1);}m_grid.initize();m_coordsys.initize();
}void glwidget::resizeGL(int w, int h)
{m_camera.setRatio((float)width() / (float)height());
}void glwidget::paintGL()
{glClearColor(1.0f, 1.0f, 1.0f, 0.1f);glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);glPolygonMode(GL_FRONT, GL_LINE);QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();QMatrix4x4 mMatrix;m_camera.setMatPro(fov);m_grid.paint(f,m_camera.getMatPro(),m_camera.getMatView(),mMatrix);m_coordsys.paint(f,m_camera.getMatPro(),m_camera.getMatView(),mMatrix);m_render.render(f,m_camera,mMatrix);
}void glwidget::mousePressEvent(QMouseEvent *event){mouse_pos_x_old = event->pos().x();mouse_pos_y_old = event->pos().y();mouse_button = event->button();
}void glwidget::mouseMoveEvent(QMouseEvent *event){int x = event->pos().x();int y = event->pos().y();double w = width(), h = height();double d_x = (mouse_pos_x_old - x)/w;double d_y = (mouse_pos_y_old - y)/h;if (mouse_button == Qt::LeftButton){foreach (int key, keys) {switch (key) {case Qt::Key_Control:m_camera.rotateY(-d_x*180*5);m_camera.rotateX(-d_y*180*5);break;case Qt::Key_Shift:m_camera.move(5*d_x, 5*d_y, 0);break;default:break;}}}mouse_pos_x_old = x;mouse_pos_y_old = y;this->repaint();
}void glwidget::mouseReleaseEvent(QMouseEvent *event){ //鼠标左键松开禁止改变相机视角changeview_ = false;
}void glwidget::wheelEvent(QWheelEvent *event){if (event->delta() > 0)fov-=2.0f;elsefov+=2.0f;if (fov<0.50f)fov = 0.5f;if (fov>=200.f)fov = 200.0;this->repaint();
}void glwidget::keyReleaseEvent(QKeyEvent *event)
{if(!event->isAutoRepeat()) //判断如果不是长按时自动触发的释放,就将key值从容器中删除keys.removeAll(event->key());if(keys.isEmpty()) //容器空了,关闭定时器keyRespondTimer->stop();
}void glwidget::keyPressEvent(QKeyEvent *event)
{if(!event->isAutoRepeat()) //判断如果不是长按时自动触发的按下,就将key值加入容器keys.append(event->key());if(!keyRespondTimer->isActive()) //如果定时器不在运行,就启动一下keyRespondTimer->start(4);
}void glwidget::slotTimeOut(){foreach (int key, keys) {switch (key) {case Qt::Key_D:break;case Qt::Key_Shift:modifier_key = Qt::Key_Shift;break;default:break;}}
}
鼠标交互功能
交互的功能实现是重载QOpenGLWidget的鼠标事件函数,修改相应的矩阵,也就对应最上面的相机变化、灯光变化等;
obj格式介绍
OBJ文件(.obj)包含有关3D对象的几何体的信息,下面是一个长方体的obj格式文件;
#
# Object file
#
mtllib Cube3x3x10.mtl
# Cube3x3x10\实体
v 0 0 0
v 3.00000002607703 0 0
v 3.00000002607703 0 9.99999977648258
v 0 0 9.99999977648258
v 3.00000002607703 3.00000002607703 9.99999977648258
v 0 3.00000002607703 9.99999977648258
v 0 0 9.99999977648258
v 3.00000002607703 0 9.99999977648258
v 0 3.00000002607703 9.99999977648258
v 0 3.00000002607703 0
v 0 0 0
v 0 0 9.99999977648258
v 0 3.00000002607703 0
v 3.00000002607703 3.00000002607703 0
v 3.00000002607703 0 0
v 0 0 0
v 3.00000002607703 3.00000002607703 0
v 3.00000002607703 3.00000002607703 9.99999977648258
v 3.00000002607703 0 9.99999977648258
v 3.00000002607703 0 0
v 3.00000002607703 3.00000002607703 9.99999977648258
v 3.00000002607703 3.00000002607703 0
v 0 3.00000002607703 0
v 0 3.00000002607703 9.99999977648258
vn 0 -1 0
vn 0 -1 0
vn 0 -1 0
vn 0 -1 0
vn 0 0 1
vn 0 0 1
vn 0 0 1
vn 0 0 1
vn -1 0 0
vn -1 0 0
vn -1 0 0
vn -1 0 0
vn 0 0 -1
vn 0 0 -1
vn 0 0 -1
vn 0 0 -1
vn 1 0 0
vn 1 0 0
vn 1 0 0
vn 1 0 0
vn 0 1 0
vn 0 1 0
vn 0 1 0
vn 0 1 0
vt 0 0
vt 0 0.00300000002607703
vt 0.00999999977648258 0.00300000002607703
vt 0.00999999977648258 0
vt 0.00150000001303852 0.00300000002607703
vt -0.00150000001303852 0.00300000002607703
vt -0.00150000001303852 0
vt 0.00150000001303852 0
vt 0.00499999988824129 0.00300000002607703
vt -0.00499999988824129 0.00300000002607703
vt -0.00499999988824129 0
vt 0.00499999988824129 0
vt 0.00150000001303852 0.00300000002607703
vt -0.00150000001303852 0.00300000002607703
vt -0.00150000001303852 0
vt 0.00150000001303852 0
vt 0.00499999988824129 0.00300000002607703
vt -0.00499999988824129 0.00300000002607703
vt -0.00499999988824129 0
vt 0.00499999988824129 0
vt 0.00999999977648258 0.00300000002607703
vt 0 0.00300000002607703
vt 0 0
vt 0.00999999977648258 0
o Cube3x3x10\实体
s off
# face 0
f 2/2/2 3/3/3 1/1/1
f 1/1/1 3/3/3 4/4/4
# face 1
f 5/5/5 6/6/6 8/8/8
f 8/8/8 6/6/6 7/7/7
# face 2
f 9/9/9 10/10/10 12/12/12
f 12/12/12 10/10/10 11/11/11
# face 3
f 13/13/13 14/14/14 16/16/16
f 16/16/16 14/14/14 15/15/15
# face 4
f 17/17/17 18/18/18 20/20/20
f 20/20/20 18/18/18 19/19/19
# face 5
f 21/21/21 22/22/22 24/24/24
f 24/24/24 22/22/22 23/23/23
其中
- mtllib Cube3x3x10.mtl 表示引用的材质文件的文件名
- v 0 0 0 表示一个点的xyz坐标,使用空格隔开
- vn 0 -1 0 表示一个点的法向量,使用空格隔开
- vt 0.09 0 表示uv纹理坐标,使用空格隔开
- f 2/2/2 3/3/3 1/1/1 表示一个面, 2/2/2依次为顶点索引,纹理坐标索引,法向索引,因为是三角面片,所以是三组,也有超多三个点的,自己造轮子的时候要注意
- o Cube 表示指定了模型名称为Cube
- s off 表示关闭光滑组
void ObjLoader::load(QString filename)
{// 打开文件QFile file(filename);if(!file.open(QIODevice::ReadOnly)){qDebug()<<"[Error] fail to open file: "<< filename;return;}// 读取文件QTextStream ts(&file);// 临时存储QVector<QVector3D> v;QVector<QVector3D> vn;QVector<QVector3D> vt;QVector<QStringList> str_faces;QVector<Face> faces;while(!ts.atEnd()){QStringList list = ts.readLine().split(QRegExp("(\\s+)"));list.removeAll(" ");if(list.size() == 0 )break;if(list[0] == "v")v.push_back(QVector3D(list[1].toFloat(),list[2].toFloat(),list[3].toFloat()));if(list[0] == "vn")vn.push_back(QVector3D(list[1].toFloat(),list[2].toFloat(),list[3].toFloat()));if(list[0] == "vt")vt.push_back(QVector3D(list[1].toFloat(),list[2].toFloat(),0));if(list[0] == "f")str_faces.push_back(list);}for( int i =0; i< str_faces.size(); i++){Face face;for( int j = 1; j<=3; j++) //obj中顶点索引是从1开始{QStringList list = str_faces[i][j].split("/");face.v[j-1] = list[0].toInt() -1;face.t[j-1] = list[1].toInt() -1;face.n[j-1] = list[2].toInt() -1;}faces.push_back(face);}for( int i = 0; i<faces.size();i++){QVector3D a,b,c,na,nb,nc;a = v[faces[i].v[0]];b = v[faces[i].v[1]];c = v[faces[i].v[2]];na = vn[faces[i].n[0]];nb = vn[faces[i].n[1]];nc = vn[faces[i].n[2]];mv.push_back(a.x());mv.push_back(a.y());mv.push_back(a.z());mv.push_back(b.x());mv.push_back(b.y());mv.push_back(b.z());mv.push_back(c.x());mv.push_back(c.y());mv.push_back(c.z());mn.push_back(na.x());mn.push_back(na.y());mn.push_back(na.z());mn.push_back(nb.x());mn.push_back(nb.y());mn.push_back(nb.z());mn.push_back(nc.x());mn.push_back(nc.y());mn.push_back(nc.z());}file.close();std::cout<< filename.toStdString() <<" id succeded!\t model_size:"<<mv.size()/3<<std::endl;
}
效果
暂时还不会弄视频或者动态图,先看个静态效果吧
bunny
Cayman_GT
相关文章:

基于QT使用OpenGL,加载obj模型,进行鼠标交互
目录 功能分析(需求分析)技术点分析OpenGL立即渲染模式可编程渲染管线模式 QOpenGLWidget派生类 glwidget逻辑glwidget.hglwidget.cpp 鼠标交互功能obj格式介绍 效果bunnyCayman_GT 功能分析(需求分析) 基于QT平台,使…...

三大赛题指南发布!2023 冬季波卡黑客松本周末开启 Workshop
2023 年一众黑客松赛事中,为什么我们建议您选择波卡黑客松大赛?或许答案在于——作为开发者极度友好的技术生态,波卡能够从参赛者的立场出发,为大家提供从 0 到 1 实现项目孵化成长的机会。这里聚集了一线技术专家的资源力量&…...
数据结构与算法(Java版) | 算法的空间复杂度简介
关于算法的空间复杂度,下面我给大家作一个简单介绍。 类似于时间复杂度的讨论,一个算法的空间复杂度(Space Complexity)定义为该算法所耗费的存储空间,同样,它也是问题规模n的一个函数。 其实,…...
大数据-之LibrA数据库系统告警处理(ALM-12037 NTP服务器异常)
告警解释 当NTP服务器异常时产生该告警。 当NTP服务器异常消除时,该告警恢复。 告警属性 告警ID 告警级别 可自动清除 12037 严重 是 告警参数 参数名称 参数含义 ServiceName 产生告警的服务名称。 RoleName 产生告警的角色名称。 HostName 异常N…...

烟草5G智慧工厂数字孪生可视化平台,赋能烟草工业数字化智慧转型
随着卷烟工厂提质增效需求增强,信息化建设推进及生产制造系统智能化改革发展,各生产单元逐步升级完善数字化,最终实现智能制造成为必然趋势。因此,5G卷烟加工工厂的数字化转型迫在眉睫。中国烟草制造行业正迈向全新的市场经济时代…...

PHP编写采集药品官方数据的程序
在 PHP 中编写爬虫程序,首先我们需要引入一些必要的库,如 curl 和 file_get_contents。然后,我们需要设置爬虫ip信息,以便我们可以从指定的爬虫ip服务器上获取数据。 // 引入必要的库 require_once curl.php;// 设置爬虫ip信息 $p…...

解决Jenkins执行git脚本时报错:No such device or address问题
问题现象: Jenkins执行BeanShell脚本时,报错:jenkins fatal: could not read Username for http://112.11.120.1: No such device or address 解决方案: 解决服务器拉取git仓库的代码权限,使用高级子模块克隆功能。…...

LCD英文字模库(16x8)模拟测试程序
字模 字模,就是把文字符号转换为LCD能识别的像素点阵信息。 电子发烧友可能都熟悉字模的用途。就是调用者通过向LCD模块发送字模数据,LCD根据字模数据在LCD面板上相应的像素描绘出图形或文字。 现在,大部分的LCD都内置了字模库,…...
二分法
文章目录 二分法概述二分 > value最左的位置二分 < value最右的位置局部最小值问题 二分法概述 什么是二分法呢?相信大家都有所了解,举个最经典的二分的例子。 给定一个整型有序数组,和一个值 v a l u e value value,如…...

Linux文件类型与权限及其修改
后面我们写代码时,写完可能会出现没有执行权限什么的,所以我们要知道文件都有哪些权限和类型。 首先 就像我们之前目录结构图里面有个/dev,它就是存放设备文件的,也就是说,哪怕是一个硬件设备,例如打印机啥的…...

RPC 框架 openfeign 介绍和学习使用总结
一、基本概念 RPC 远程过程调用(Remote Procedure Call)的缩写形式 Birrell 和 Nelson 在 1984 发表于 ACM Transactions on Computer Systems 的论文《Implementing remote procedure calls》对 RPC 做了经典的诠释。 RPC 是指计算机 A 上的进程&am…...
大厂真题:【DP/贪心】字节跳动2023秋招-小红的 01 串
题目描述与示例 题目描述 小红拿到了一个 01 串,她准备将若干个字符1 染成红色,将若干个字符0 染成蓝色,但有个限制:如果一个0 和一个1 相邻,那么它们不能同时染色。 小红想知道,最多可以染多少个字符&a…...

【技术类-01】doc转PDF程序卡死的解决方案,
摘要: 1、报错: raise AttributeError("%s.%s" % (self._username_, attr))) 2、表现:doc转PDF卡死(白条不动或出现以上英文) 3、解决:在docx保存代码行后面加上time.sleep(3) 4、…...

探索未来,开启无限可能:打造智慧应用,亚马逊云科技大语言模型助您一臂之力
文章目录 什么是大模型?大模型训练方法亚马逊云科技推出生成式AI新工具 —— aws toolkit使用教程 总结 什么是大模型? 近期,生成式大模型是人工智能领域的研究热点。这些生成式大模型,诸如文心一言、文心一格、ChatGPT、Stable …...

HTML点击链接强制触发下载
常见网页中会有很多点击链接即下载的内容,以下示范一下如何实现 <a href"文件地址" download"下载的文件名字(不包括后缀)">强制下载</a> 下面举个例子: <a href"./image/test.jpg"…...

Paimon 与 Spark 的集成(一)
Paimon Apache Paimon (incubating) 是一项流式数据湖存储技术,可以为用户提供高吞吐、低延迟的数据摄入、流式订阅以及实时查询能力。Paimon 采用开放的数据格式和技术理念,可以与 ApacheFlink / Spark / Trino 等诸多业界主流计算引擎进行对接…...
批量导入SQL Server中的建表、建存储过程和建调度作业的文件
要批量导入SQL Server中的建表、建存储过程和建调度作业的文件,可以按照以下步骤进行操作: 确保你拥有适当的权限:在导入这些文件之前,请确保你具有足够的权限来创建表、存储过程和调度作业。通常需要具备数据库管理员(…...

启动Hbase出现报错
报错信息:slave1:head: cannot open/usr/local/hbase-2.3.1/bin/../logs/hbasewanggiqi-regionserver-slavel.out’ for reading: No such file or direslave2: head: cannot open/usr/local/hbase-2.3.1/bin/../logs/hbasewangqiqi-regionserver-slave2.out’ for …...
【数据结构】——栈、队列简答题模板
目录 一、栈(一)栈的基本概念(二)栈的应用(三)栈的代码实现(四)递归算法(五)栈与队列的区别 二、队列(一)队列的基本概念(…...

基于若依的ruoyi-nbcio流程管理系统仿钉钉流程json转bpmn的flowable的xml格式(排它条件网关)
更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 这个章节来完成并行网关与排它条件网关的功能 1、前端 目前就修改了排它条件网关的前端条件部分…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...

基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...

认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
智能职业发展系统:AI驱动的职业规划平台技术解析
智能职业发展系统:AI驱动的职业规划平台技术解析 引言:数字时代的职业革命 在当今瞬息万变的就业市场中,传统的职业规划方法已无法满足个人和企业的需求。据统计,全球每年有超过2亿人面临职业转型困境,而企业也因此遭…...