WebGL层次模型——单节点模型
目录
多个简单模型组成的复杂模型
层次结构模型
单关节模型
JointModel程序中模型的层次结构
示例程序(JointMode.js)
代码详解
绘制层次模型(draw())
程序效果
多个简单模型组成的复杂模型
绘制由多个小部件组成的复杂模型,最关键的问题是如何处理模型的整体移动,以及各个小部件间的相对移动。现在就来研究这个问题。首先,考虑一下人类的手臂:从肩部到指尖,包括上臂(肘以上)、前臂(肘以下)、手掌和手指,如下图所示。
手臂的每个部分可以围绕关节运动,如上图所示:
● 上臂可以绕肩关节旋转运动,并带动前臂、手掌和手指一起运动。
● 前臂可以绕肘关节运动,并带动手掌和手指一起运动,但不影响上臂。
● 手掌绕腕关节运动,并带动手指一起运动,但不影响上臂和前臂。
● 手指运动不影响上臂、前臂和手掌。
总之,当手臂的某个部位运动时,位于该部位以下的其他部位会随之一起运动,而位于该部位以上的其他部位不受影响。此外,这里的所有运动,都是围绕某个关节(肩关节、肘关节、腕关节、指关节)的转动。
层次结构模型
绘制机器人手臂这样一个复杂的模型,最常用的方法就是按照模型中各个部件的层次顺序,从高到低逐一绘制,并在每个关节上应用模型矩阵。比如,在图9.2中,肩关节、肘关节、腕关节,指关节都有各自的旋转矩阵。
注意,三维模型和现实中的人类或机器人不一样,它的部件并没有真正连接在一起。如果直接转动上臂,那么肘部以下的部分,包括前臂、手掌和手指,只会留在原地,这样手臂就断开了。所以,当上臂绕肩关节转动时,你需要在代码中实现“肘部以下部分跟随上臂转动”的逻辑。具体地,上臂绕肩关节转动了多少度,肘部以下的部分也应该绕肩关节转动多少度。
当情况较为简单时,实现“部件A转动带动部件B转动”可以很直接,只要对部件B也施以部件A的旋转矩阵即可。比如,使用模型矩阵使上臂绕肩关节转动30度,然后在绘制肘关节以下的各部位时,为它们施加同一个模型矩阵,也令其绕肩关节转动30度,如下图所示。这样,肘关节以下的部分就能自动跟随上臂转动了。
肘部以下部分随着上臂转动
如果情况更复杂一些,比如先使上臂绕肩关节转动30度,然后使前臂绕肘关节转动10度,那么对肘关节以下的部分,你就得先施加上臂绕肩关节转动30度的矩阵(可称为“肩关节模型矩阵”),然后再施加前臂绕肘关节转动10度的矩阵。将这两个矩阵相乘,其结果可称为“肘关节模型矩阵”,那么在绘制肘关节以下部分的时候,直接应用这个所谓的“肘关节模型矩阵”(而不考虑肩关节,因为肩关节的转动信息已经包含在该矩阵中了)作为模型矩阵就可以了。
按照上述方式编程,三维场景中的肩关节就能影响肘关节,使得上臂的运动带动前臂的运动;反过来,不管前臂如何运动都不会影响上臂。这就与现实中的情况相符合了。
现在,你已经对这种由多个小模型组成的复杂模型的运动规律有了一些了解,下面来看一下示例程序。
单关节模型
先来看一个单关节模型的例子。示例程序JointModel绘制了一个仅由两个立方体部件组成的机器人手臂,其运行结果如下图(左)所示;手臂的两个部件为arm1与arm2,arm1接在arm2的上面,如下图(右)所示。你可以把arm1想象成上臂,而把arm2想象成前臂,而肩关节在最下面(上臂在下而前臂在上,是为了以后加入手掌和手指后看得更清楚)。
JointModel程序中模型的层次结构
运行程序,用户可以使用左右方向键控制arm1(同时带动整条手臂)水平转动,使用上下方向键控制arm2绕joint1关节垂直转动。比如,先按下方向键,arm2逐渐向前倾斜(下图左),然后按右方向键,arm1向右旋转(下图右)。
如你所见,arm2绕joint1的转动并不影响arm1,而arm1的转动会带动arm2一起转动。
示例程序(JointMode.js)
如下显示了JointMode.js的代码,所有用来绘制和控制机器人手臂的逻辑都在JavaScript代码中。
var VSHADER_SOURCE = // p316'attribute vec4 a_Position;\n' +'attribute vec4 a_Normal;\n' +'uniform mat4 u_MvpMatrix;\n' + // 模型视图投影矩阵'uniform mat4 u_NormalMatrix;\n' + // 用于改变法向量的矩阵'uniform vec3 u_LightColor;\n' + // 平行光颜色'uniform vec3 u_AmbientColor;\n' + // 环境光颜色'varying vec4 v_Color;\n' +'void main() {\n' +' gl_Position = u_MvpMatrix * a_Position;\n' +// 光照计算,使场景更加逼真' vec3 lightDirection = normalize(vec3(0.0, 0.5, 0.7));\n' + // 归一化光线方向' vec4 color = vec4(1.0, 0.4, 0.0, 1.0);\n' + // 物体颜色' vec3 normal = normalize((u_NormalMatrix * a_Normal).xyz);\n' + // 归一化法向量' float nDotL = max(dot(normal, lightDirection), 0.0);\n' + // 点积 cos' vec3 ambient = u_AmbientColor * color.rgb;\n' + // 环境反射光颜色' vec3 diffuse = u_LightColor * color.rgb * nDotL;\n' + // 漫反射光颜色' v_Color = vec4(diffuse + ambient, color.a);\n' + // 最终颜色'}\n';var FSHADER_SOURCE ='#ifdef GL_ES\n' +'precision mediump float;\n' +'#endif\n' +'varying vec4 v_Color;\n' +'void main() {\n' +' gl_FragColor = v_Color;\n' +'}\n';function main() {var canvas = document.getElementById('webgl');var gl = getWebGLContext(canvas);if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) returnvar n = initVertexBuffers(gl);gl.clearColor(0.0, 0.0, 0.0, 1.0);gl.enable(gl.DEPTH_TEST);var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');var u_NormalMatrix = gl.getUniformLocation(gl.program, 'u_NormalMatrix');var u_LightColor = gl.getUniformLocation(gl.program, 'u_LightColor');var u_AmbientColor = gl.getUniformLocation(gl.program, 'u_AmbientColor');gl.uniform3f(u_LightColor, 1.0, 1.0, 1.0);gl.uniform3f(u_AmbientColor, 0.0, 1.0, 1.0);// 计算视图投影矩阵var viewProjMatrix = new Matrix4();viewProjMatrix.setPerspective(50.0, canvas.width / canvas.height, 1.0, 100.0);viewProjMatrix.lookAt(20.0, 10.0, 30.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);// 注册键盘事件响应函数document.onkeydown = function(ev){ keydown(ev, gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); };draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // 绘制立方体
}var ANGLE_STEP = 3.0; // 每次按键转动的角度
var g_arm1Angle = 0.0; // arml的当前角度
var g_joint1Angle = 0.0; // joint1的当前角度(即arm2的角度)
function keydown(ev, gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {switch (ev.keyCode) {case 38: // 上方向键 -> joint1绕z轴正向转动if (g_joint1Angle < 135.0) g_joint1Angle += ANGLE_STEP;break;case 40: // 下方向键 -> joint1绕z轴负向转动if (g_joint1Angle > -135.0) g_joint1Angle -= ANGLE_STEP;break;case 39: // 右方向键 -> arm1绕Y轴正方向转动g_arm1Angle = (g_arm1Angle + ANGLE_STEP) % 360;break;case 37: // 左方向键 -> arm1绕Y轴负方向转动g_arm1Angle = (g_arm1Angle - ANGLE_STEP) % 360;break;default: return;}// 绘制手臂draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix);
}
function initVertexBuffers(gl) {// 顶点坐标(长方体宽度3.0,高度10.0,长度3.0,原点位于底部中心)var vertices = new Float32Array([1.5, 10.0, 1.5, -1.5, 10.0, 1.5, -1.5, 0.0, 1.5, 1.5, 0.0, 1.5, // v0-v1-v2-v3 front1.5, 10.0, 1.5, 1.5, 0.0, 1.5, 1.5, 0.0,-1.5, 1.5, 10.0,-1.5, // v0-v3-v4-v5 right1.5, 10.0, 1.5, 1.5, 10.0,-1.5, -1.5, 10.0,-1.5, -1.5, 10.0, 1.5, // v0-v5-v6-v1 up-1.5, 10.0, 1.5, -1.5, 10.0,-1.5, -1.5, 0.0,-1.5, -1.5, 0.0, 1.5, // v1-v6-v7-v2 left-1.5, 0.0,-1.5, 1.5, 0.0,-1.5, 1.5, 0.0, 1.5, -1.5, 0.0, 1.5, // v7-v4-v3-v2 down1.5, 0.0,-1.5, -1.5, 0.0,-1.5, -1.5, 10.0,-1.5, 1.5, 10.0,-1.5 // v4-v7-v6-v5 back]);// 法向量var normals = new Float32Array([0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up-1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, // v7-v4-v3-v2 down0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0, 0.0, 0.0,-1.0 // v4-v7-v6-v5 back]);// 顶点的索引var indices = new Uint8Array([0, 1, 2, 0, 2, 3, // front4, 5, 6, 4, 6, 7, // right8, 9,10, 8,10,11, // up12,13,14, 12,14,15, // left16,17,18, 16,18,19, // down20,21,22, 20,22,23 // back]);// 将顶点属性写入缓冲区(坐标和法线)if (!initArrayBuffer(gl, 'a_Position', vertices, gl.FLOAT, 3)) return -1;if (!initArrayBuffer(gl, 'a_Normal', normals, gl.FLOAT, 3)) return -1;gl.bindBuffer(gl.ARRAY_BUFFER, null);var indexBuffer = gl.createBuffer();gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);return indices.length;
}function initArrayBuffer(gl, attribute, data, type, num) {var buffer = gl.createBuffer();gl.bindBuffer(gl.ARRAY_BUFFER, buffer);gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);var a_attribute = gl.getAttribLocation(gl.program, attribute);gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);gl.enableVertexAttribArray(a_attribute);return true;
}// 坐标变换矩阵
var g_modelMatrix = new Matrix4(), g_mvpMatrix = new Matrix4();function draw(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);// Arm1var arm1Length = 10.0; // Length of arm1g_modelMatrix.setTranslate(0.0, -12.0, 0.0);g_modelMatrix.rotate(g_arm1Angle, 0.0, 1.0, 0.0); // 绕y轴旋转drawBox(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // 绘制// Arm2g_modelMatrix.translate(0.0, arm1Length, 0.0); // 移动至joint1处g_modelMatrix.rotate(g_joint1Angle, 0.0, 0.0, 1.0); // 绕z轴旋转g_modelMatrix.scale(1.3, 1.0, 1.3); // 使立方体粗一点drawBox(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix); // 绘制
}var g_normalMatrix = new Matrix4(); // 法线的旋转矩阵// 绘制立方体
function drawBox(gl, n, viewProjMatrix, u_MvpMatrix, u_NormalMatrix) {// 计算模型视图投影矩阵并传给u_MvpMatrix变量g_mvpMatrix.set(viewProjMatrix);g_mvpMatrix.multiply(g_modelMatrix); // 模型 视图投影 相乘得到最终矩阵gl.uniformMatrix4fv(u_MvpMatrix, false, g_mvpMatrix.elements);// 计算法线变换矩阵并传给u_NormalMatrix变量g_normalMatrix.setInverseOf(g_modelMatrix);g_normalMatrix.transpose();gl.uniformMatrix4fv(u_NormalMatrix, false, g_normalMatrix.elements);// 最终绘制gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}
代码详解
主要的变化发生在initVertexBuffers()函数,它将arm1和arm2的数据写入了相应的缓冲区。以前程序中的立方体都是以原点为中心,且边长为2.0;本例为了更好地模拟机器人手臂,使用如下图所示的立方体,原点位于底面中心,底面是边长为3.0的正方形,高度为10.0。将原点置于立方体的底面中心,是为了便于使立方体绕关节转动(比如,肘关节就位于前臂立方体的底面中心),如上图所示。arm1和arm2都使用这个立方体。
用来绘制机器人前臂和上臂的立方体
main()函数首先根据可视空间,视点和视线方向计算出了视图投影矩阵viewProjMatrix(第44~46行)。
然后在键盘事件响应函数中调用keydown()函数(第48行),通过方向键控制机器人的手臂运动。
接着定义keydown()函数本身(第55行),以及若干该函数需要用到的全局变量(第52、53和54行)。
ANGLE_STEP常量(第52行)表示每一次按下按键,arm1或joint1转动的角度,它的值是3.0。g_arm1Angle变量(第53行)表示arm1的当前角度,g_joint1Angle变量表示joint1的(也就是arm2的)当前角度,如下图所示。
g_joint1Angle和g_arm1Angle
keydown()函数(第55行)的任务是,根据按下的是哪个按键,对g_joint1Angle或g_arm1Angle变量加上或减去常量ANGLE_STEP的值。注意,joint1的转动角度只能在-135度到135度之间,这是为了不与arm1冲突。最后,draw()函数将整个机器人手臂绘制出来。
绘制层次模型(draw())
draw()函数的任务是绘制机器人手臂(第128行)。注意,draw()函数和drawBox()函数用到了全局变量g_modelMatrix和g_mvpMatrix(第126行)。
如你所见,draw()函数内部调用了drawBox()函数,每调用一次绘制一个部件,先绘制下方较细arm1,再绘制上方较粗arm2。
绘制单个部件的步骤是:(1)调用setTranslate()或translate()进行平移;(2)调用rotate()进行旋转;(3)调用drawBox()进行绘制。
绘制整个模型时,需要按照各部件的层次顺序,先arm1后arm2,再执行(1)平移,(2)旋转,(3)绘制。
绘制arm1的步骤如下:首先在模型矩阵g_modelMatrix上调用setTranslate()函数,使之平移(0.0,-12.0,0.0)到稍下方位置(第133行);然后调用rotate()函数,绕y轴旋转g_arm1Angle角度(第134行);最后调用drawBox()函数绘制arm1。
接着来绘制arm2,它与arm1在joint1处连接,如图g_joint1Angle和g_arm1Angle所示,我们应当从该处上开始绘制arm2。但是此时,模型矩阵还是处于绘制arm1的状态(向下平移并绕y轴旋转)下,所以得先调用translate()函数沿y轴向上平移arm1的高度arm1Length(第138行)。注意这里调用的是translate()而不是setTranslate(),因为这次平移是在之前的基础上进行的。
然后,使用g_joint1Angle进行肘关节处的转动(第139行),并在x和z轴稍作拉伸(第140行),使前臂看上去粗一些,以便与上臂区分开。
这样一来,每当keydown()函数更新了g_joint1Angle变量和g_arm1Angle变量的值,然后调用draw()函数进行绘制时,就能绘制出最新状态的机器人手臂,arm1的位置取决于g_arm1Angle变量,而arm2的位置取决于g_jointAngle变量(当然也受g_arm1Angle的影响)。
drawBox()函数的任务是绘制机器人手臂的某一个立方体部件,如上臂或前臂。它首先计算模型视图投影矩阵,传递给u_MvpMatrix变量(第149~151行),然后根据模型矩阵计算法向量变换矩阵,传递给u_NormalMatrix变量(第153~155行),最后绘制立方体(第157行)。
程序效果
相关文章:

WebGL层次模型——单节点模型
目录 多个简单模型组成的复杂模型 层次结构模型 单关节模型 JointModel程序中模型的层次结构 示例程序(JointMode.js) 代码详解 绘制层次模型(draw()) 程序效果 多个简单模型组成的复杂模型 绘制…...

【链表】反转链表 II-力扣 92 题
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…...

【考研数学】高等数学第六模块 —— 空间解析几何(1,向量基本概念与运算)
文章目录 引言一、空间解析几何的理论1.1 基本概念1.2 向量的运算 写在最后 引言 我自认空间想象能力较差,所以当初学这个很吃力。希望现在再接触,能好点。 一、空间解析几何的理论 1.1 基本概念 1.向量 —— 既有大小,又有方向的量称为向…...

巨人互动|Facebook海外户Facebook客户反馈分数
Facebook客户反馈分数是一项用于衡量用户对Facebook产品和服务满意度的指标。该指标被广泛应用于各种调研和评估活动,帮助Facebook了解用户对其平台和功能的意见和建议,并从中识别出改进的机会。 巨人互动|Facebook海外户&Facebook新闻提要的算法&am…...

Tomcat多实例部署和动静分离
一、多实例部署: 多实例:多实例就是在一台服务器上同时开启多个不同的服务端口,同时运行多个服务进程,这些服务进程通过不同的socket监听不同的服务端口来提供服务。 1.前期准备: 1.关闭防火墙:systemctl …...

关于 C/C++ 中在指针前加 const 关键字的作用说明
1. 作用说明: 在指针前加 const 的用途为:不可改变指针指向的内存的值,即将该指向指向的内存中的变量置为只读(read-only) 变量。 但是,可以给 const 的指针赋值,即将具有 const 属性的指针指向别的内存地…...

Vue.js新手指南:从零开始建立你的第一个应用
🌷🍁 博主猫头虎(🐅🐾)带您 Go to New World✨🍁 🦄 博客首页——🐅🐾猫头虎的博客🎐 🐳 《面试题大全专栏》 🦕 文章图文…...
【案例】--EasyExcel导入导出文件案例
目录 一、前言二、EasyExcel解析(导入)文件2.1、EasyExcel选型2.2、如何存储excel解析的文件2.3、解析格式规则的excel文件2.4、解析未知格式规则的excel文件三、EasyExcel解析(导出)文件3.1、导出基本代码实现一、前言 最近项目中,需要对excel、csv等文件进行解析,并做相关…...

深入探索图像处理:从基础到高级应用
💂 个人网站:【工具大全】【游戏大全】【神级源码资源网】🤟 前端学习课程:👉【28个案例趣学前端】【400个JS面试题】💅 寻找学习交流、摸鱼划水的小伙伴,请点击【摸鱼学习交流群】 图像处理是计算机视觉领…...

Jetpack Compose基础组件 - Image
Image的源码参数预览 Composable fun Image(painter: Painter,contentDescription: String?,modifier: Modifier Modifier,alignment: Alignment Alignment.Center,contentScale: ContentScale ContentScale.Fit,alpha: Float DefaultAlpha,colorFilter: ColorFilter? …...
UINavigationController内的页面跳转实现 UIViewController 的 present和dismiss动画
UINavigationController内部页面跳转默认为左右切换,但是当我们想向上弹出进入界面,或者向下离开界面时,需要实现UINavigationControllerDelegate 协议自行控制页面的动画(否则直接在navVc上叠加动画会导致动画结束后的那个页面,自…...

PMP对项目管理工作有什么用?
首先,项目管理岗位基本是不限行业的,所以,只要是项目管理相关的岗位,pmp证书都是能起到效果的,不用担心局限性太大,而且,pmp证书是国际证书,无论国企还是外企,都是认可这…...
Python 将‘20230919182550‘ 转换为 ‘%Y年%m月%d日 %H:%M‘
为了将给定的时间字符串 cur_time 转换为指定的格式,可以使用 Python 的 datetime 模块。以下是完成此操作的步骤: 使用 strptime 方法将 cur_time 转换为一个 datetime 对象。使用 strftime 方法将这个 datetime 对象转换为所需的格式。 这是具体的代…...
vue2.0检测无用的代码并删除
(1)、使用 useless-files-webpack-plugin 来查找无用文件 npm i useless-files-webpack-plugin -S (2)、vue.config.js中配置 const UselessFile require(useless-files-webpack-plugin)chainWebpack: config > {config.plu…...

小米华为,化干戈为玉帛!
近日来,手机圈又掀起了各大厂家推出新品的高潮。首先是华为Mate60的推出,其自研的麒麟9000S芯片瞬间点燃了国内手机市场,得到了国内甚至国外业界人士的认可和好评。 而近日网上盛传的小米创始人雷军的“愿意加入华为技术生态圈”的邀请&…...

【文末赠书】SRE求职必会 —— 可观测性平台可观测性工程(Observability Engineering)
文章目录 〇、导读一、实现可观测性平台的技术要点是什么?二、兼容全域信号量三、所谓全域信号量有哪些?四、统一采集和上传工具五、统一的存储后台六、自由探索和综合使用数据七、总结★推荐阅读《可观测性工程》直播预告直播主题直播时间预约直播 视频…...
content生成自定义图标的方式是什么?
animate.css是一个跨浏览器的CSS3动画库,它内置了很多经典的CSS3动画。使用起来很方便。下面我们通过例子讲解如何使用自定义类名和animate.css库实现动画效果。 (1)从animate.css官方网站获取animate.css文件,保存到chapter04目录中。 (2)创建C:\vue\…...

无涯教程-JavaScript - SECH函数
描述 SECH函数返回某个Angular的双曲正割。双曲正割是双曲余弦的倒数。因此,双曲正割的值由等式给出- $$\sinh\left(x\right)\frac {1} {\cosh\left(x\right)} \frac {2} {e ^ x e ^ {-x}} $$ 语法 SECH (number)争论 Argument描述Required/OptionalNumberNumber is the …...

天宇微纳芯片ic测试软件如何测试芯片上下电功能?
芯片的上电与下电功能测试是集成电路生产和研发过程中的关键环节,可以帮助企业确保产品的可靠性、整合性和兼容性,同时提高生产效率和产品质量。 因此在芯片的研发设计中,企业会对芯片的上下电有严格的要求,包括上下电的时序&…...

1万多爱背句子英语口语ACCESS\EXCEL数据库
今天这个数据库包含3个表,一个是分类表,一个是分类章节有,一个是具体句子表,表与表之间可以根据相关ID进行关联,是一个学习英语的好数据,具体请查收截图或样本: 数据有ACCESS数据库文件…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...

《基于Apache Flink的流处理》笔记
思维导图 1-3 章 4-7章 8-11 章 参考资料 源码: https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚会及会议 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...