当前位置: 首页 > news >正文

WebGL 选中一个表面

目录

选中一个表面 

示例程序(PickFace.js)

代码详解

gl.readPixels()见126行效果

gl.UNSIGNED_BYTE注意点

示例效果


选中一个表面 

​​​​​​​WebGL 选中物体_山楂树の的博客-CSDN博客可以使用同样的方法来选中物体的某一个表面。这一节在PickObject程序的基础上编写了PickFace程序,后者同样包含一个立方体,但用户可以选中立方体的某一个表面,被选中的表面会变成白色。下图显示了PickFace的运行效果。

如果你理解了PickObject的程序原理,那么PickFace就很简单了。PickObject用户在点击鼠标时,将立方体重绘为红色,然后读取鼠标点击位置的像素颜色,根据其是红色或是黑色来判断点击时鼠标是否在立方体上,即是否选中了立方体。而PickFace则更进一步,在用户点击鼠标时重绘立方体,并将“每个像素属于哪个面”的信息写入到颜色缓冲区的α分量中。下面来看一下示例程序。 

示例程序(PickFace.js)

PickFace.js的代码如下所示。为了简洁,略去了与前例相同的部分,如顶点着色器等。 

var VSHADER_SOURCE ='attribute vec4 a_Position;\n' +'attribute vec4 a_Color;\n' +'attribute float a_Face;\n' +   // 曲面编号(不能将int用于属性变量)'uniform mat4 u_MvpMatrix;\n' +'uniform int u_PickedFace;\n' + // 选定面的曲面编号'varying vec4 v_Color;\n' +'void main() {\n' +'  gl_Position = u_MvpMatrix * a_Position;\n' +'  int face = int(a_Face);\n' + // 转换为int   1.0 -> 1'  vec3 color = (face == u_PickedFace) ? vec3(1.0) : a_Color.rgb;\n' +'  if(u_PickedFace == 0) {\n' + // 如果为0,则将面编号插入alpha'    v_Color = vec4(color, a_Face/255.0);\n' + // 6.0/255.0 = 6'  } else {\n' +'    v_Color = vec4(color, a_Color.a);\n' +'  }\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';var ANGLE_STEP = 20.0; // 旋转角度(20度/秒)
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_PickedFace = gl.getUniformLocation(gl.program, 'u_PickedFace');var viewProjMatrix = new Matrix4();viewProjMatrix.setPerspective(30.0, canvas.width / canvas.height, 1.0, 100.0);viewProjMatrix.lookAt(0.0, 0.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);gl.uniform1i(u_PickedFace, -1); // 默认不匹配面var currentAngle = 0.0; // 当前旋转角度canvas.onmousedown = function(ev) {   // 按下鼠标var x = ev.clientX, y = ev.clientY;var rect = ev.target.getBoundingClientRect();if (rect.left <= x && x < rect.right && rect.top <= y && y < rect.bottom) {// 如果按下的位置在<canvas>内,则更新表面var x_in_canvas = x - rect.left, y_in_canvas = rect.bottom - y;var face = checkFace(gl, n, x_in_canvas, y_in_canvas, currentAngle, u_PickedFace, viewProjMatrix, u_MvpMatrix);gl.uniform1i(u_PickedFace, face); // 将曲面编号传递给uPickedFacedraw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix);}}var tick = function() {   // Start drawingcurrentAngle = animate(currentAngle);draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix);requestAnimationFrame(tick, canvas);};tick();
}function initVertexBuffers(gl) {//    v6----- v5//   /|      /|//  v1------v0|//  | |     | |//  | |v7---|-|v4//  |/      |///  v2------v3var vertices = new Float32Array([   // Vertex coordinates1.0, 1.0, 1.0,  -1.0, 1.0, 1.0,  -1.0,-1.0, 1.0,   1.0,-1.0, 1.0,    // v0-v1-v2-v3 front1.0, 1.0, 1.0,   1.0,-1.0, 1.0,   1.0,-1.0,-1.0,   1.0, 1.0,-1.0,    // v0-v3-v4-v5 right1.0, 1.0, 1.0,   1.0, 1.0,-1.0,  -1.0, 1.0,-1.0,  -1.0, 1.0, 1.0,    // v0-v5-v6-v1 up-1.0, 1.0, 1.0,  -1.0, 1.0,-1.0,  -1.0,-1.0,-1.0,  -1.0,-1.0, 1.0,    // v1-v6-v7-v2 left-1.0,-1.0,-1.0,   1.0,-1.0,-1.0,   1.0,-1.0, 1.0,  -1.0,-1.0, 1.0,    // v7-v4-v3-v2 down1.0,-1.0,-1.0,  -1.0,-1.0,-1.0,  -1.0, 1.0,-1.0,   1.0, 1.0,-1.0     // v4-v7-v6-v5 back]);var colors = new Float32Array([   // Colors0.32, 0.18, 0.56,  0.32, 0.18, 0.56,  0.32, 0.18, 0.56,  0.32, 0.18, 0.56, // v0-v1-v2-v3 front0.5, 0.41, 0.69,   0.5, 0.41, 0.69,   0.5, 0.41, 0.69,   0.5, 0.41, 0.69,  // v0-v3-v4-v5 right0.78, 0.69, 0.84,  0.78, 0.69, 0.84,  0.78, 0.69, 0.84,  0.78, 0.69, 0.84, // v0-v5-v6-v1 up0.0, 0.32, 0.61,   0.0, 0.32, 0.61,   0.0, 0.32, 0.61,   0.0, 0.32, 0.61,  // v1-v6-v7-v2 left0.27, 0.58, 0.82,  0.27, 0.58, 0.82,  0.27, 0.58, 0.82,  0.27, 0.58, 0.82, // v7-v4-v3-v2 down0.73, 0.82, 0.93,  0.73, 0.82, 0.93,  0.73, 0.82, 0.93,  0.73, 0.82, 0.93, // v4-v7-v6-v5 back]);var faces = new Uint8Array([   // Faces1, 1, 1, 1,     // v0-v1-v2-v3 front2, 2, 2, 2,     // v0-v3-v4-v5 right3, 3, 3, 3,     // v0-v5-v6-v1 up4, 4, 4, 4,     // v1-v6-v7-v2 left5, 5, 5, 5,     // v7-v4-v3-v2 down6, 6, 6, 6,     // v4-v7-v6-v5 back]);var indices = new Uint8Array([   // Indices of the vertices0, 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]);var indexBuffer = gl.createBuffer();if (!initArrayBuffer(gl, vertices, gl.FLOAT, 3, 'a_Position')) return -1; // Coordinates Informationif (!initArrayBuffer(gl, colors, gl.FLOAT, 3, 'a_Color')) return -1;      // Color Informationif (!initArrayBuffer(gl, faces, gl.UNSIGNED_BYTE, 1, 'a_Face')) return -1;// Surface Informationgl.bindBuffer(gl.ARRAY_BUFFER, null);gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);return indices.length;
}function checkFace(gl, n, x, y, currentAngle, u_PickedFace, viewProjMatrix, u_MvpMatrix) {var pixels = new Uint8Array(4); // 存储像素值的数组gl.uniform1i(u_PickedFace, 0);  // 将表面编号写入a分量draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix);/* 在WebGL中,gl.readPixels()函数用于从帧缓冲区中读取像素数据(而非颜色缓冲区中)。该函数的第六个参数用于指定返回的像素数据的数据类型。当将第六个参数设置为gl.UNSIGNED_BYTE时,表示希望返回的像素数据以无符号字节的形式表示。每个像素将用一个8位无符号整数(即一个字节)来表示。使用gl.UNSIGNED_BYTE可以获得每个像素的红、绿、蓝和透明度通道的值(RGB或RGBA)的整数表示,取值范围在0到255之间。当将gl.readPixels()函数的第六个参数设置为gl.UNSIGNED_BYTE时,返回的每个像素的RGBA值都会以0到255的范围内的整数表示。每个颜色通道(红、绿、蓝和透明度)都用一个8位无符号整数(即一个字节)来表示这样的设置通常用于读取像素数据后进行图像处理、纹理操作或其他需要使用整数表示的场景。*/gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels); // 读取单击位置的像素值。pixels[3]是曲面数console.log(pixels); // [128, 105, 176, 2]return pixels[3];
}var g_MvpMatrix = new Matrix4(); // 模型视图投影矩阵
function draw(gl, n, currentAngle, viewProjMatrix, u_MvpMatrix) {// 计算模型视图投影矩阵并将其传递给u_MvpMatrixg_MvpMatrix.set(viewProjMatrix); g_MvpMatrix.rotate(currentAngle, 1.0, 0.0, 0.0); // 适当旋转g_MvpMatrix.rotate(currentAngle, 0.0, 1.0, 0.0);g_MvpMatrix.rotate(currentAngle, 0.0, 0.0, 1.0);gl.uniformMatrix4fv(u_MvpMatrix, false, g_MvpMatrix.elements);gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);   gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);  
}var last = Date.now();  // 上次调用此函数的时间
function animate(angle) {var now = Date.now(); // 计算运行时间var elapsed = now - last;last = now;// 更新当前旋转角度(根据经过的时间进行调整)var newAngle = angle + (ANGLE_STEP * elapsed) / 1000.0;return newAngle % 360;
}function initArrayBuffer (gl, data, type, num, attribute) {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;
}

代码详解

首先,顶点着色器中添加了attribute变量a_Face,它表示立方体各表面的编号,即当前顶点属于哪个表面(第4行)。鼠标被点击时,这个值就会被“编码”成颜色值的α分量。initVertexBuffers()函数(第63行)建立了表面编号数组faces,数组中的每个元素对应一个顶点(第87行)。比如,顶点v0-v1-v2-v3定义了1号表面,而顶点v0-v3-v4-v5定义了2号表面,等等。数组faces前4个元素都是1,表示前4个顶点都属于1号表面,以此类推(第88行)。

当某个表面被选中时,就通过u_PickedFace变量来通知顶点着色器这个表面被选中了(第6行)。这样顶点着色器就可以将这个表面绘制成白色,用户就获得了反馈,知道这个表面确实被选中了。

在正常情况下(即不在鼠标被点击的那一刻),顶点着色器会比较当前被选中的表面编号u_PickedFace和当前顶点的表面编号a_Face,如果它们相等,即当前顶点属于被选中的表面,就将color赋为白色,如果不相等,就将其赋为顶点原来的颜色a_Color。此处必须将float类型的a_Face转化为int类型,再与u_PickedFace进行比较,因为attribute变量只能是float类型的。在鼠标点击的那一刻,u_PickedFace被设为0,我们将a_Face的值写入到颜色的α分量中。

main()函数为u_PickedFace指定初始值-1(第40行)。按照faces变量的定义(第87行),立方体各表面中没有哪一个编号是-1,所以一开始没有任何表面被选中,每一个面的颜色都是初始颜色。

gl.readPixels()见126行效果

鼠标被点击时,u_PickedFace变量变为了0,顶点着色器就在颜色缓冲区中将每个面绘制成非1的α值,并且α的值取决于表面编号。最关键的逻辑发生在鼠标点击事件的响应函数中:在获取了鼠标点击位置后,调用checkFace()函数并传入u_PickedFace变量,也就是着色器中同名变量的存储地址(第49行)。

checkFace()函数的任务是,根据点击位置返回选中表面的编号(第113行)。该函数首先将u_PickedFace变量设为0(第115行),然后立刻调用draw()函数(在颜色缓冲区中,最终没有显示在屏幕上)进行绘制,此时每个表面的α值就取决于表面的编号。然后,从颜色缓冲区获取鼠标点击处的像素值(第125行),通过pixels[3]获取表面编号(即α分量,索引为3)。checkFace()函数返回选中的表面编号,执行流程回到鼠标点击事件响应函数中,用选中的表面编号重新绘制立方体(第50~51行),并在屏幕上显示出来,如前所述。

gl.UNSIGNED_BYTE注意点

    在WebGL中,gl.readPixels()函数用于从帧缓冲区中读取像素数据(而非颜色缓冲区中)。该函数的第六个参数用于指定返回的像素数据的数据类型。

    当将第六个参数设置为gl.UNSIGNED_BYTE时,表示希望返回的像素数据以无符号字节的形式表示。每个像素将用一个8位无符号整数(即一个字节)来表示。

    使用gl.UNSIGNED_BYTE可以获得每个像素的红、绿、蓝和透明度通道的值(RGB或RGBA)的整数表示,取值范围在0到255之间。(见 gl.readPixels()见126行效果 上图)

    这样的设置通常用于读取像素数据后进行图像处理、纹理操作或其他需要使用整数表示的场景。

示例效果

相关文章:

WebGL 选中一个表面

目录 选中一个表面 示例程序&#xff08;PickFace.js&#xff09; 代码详解 gl.readPixels()见126行效果 gl.UNSIGNED_BYTE注意点 示例效果 选中一个表面 ​​​​​​​WebGL 选中物体_山楂树の的博客-CSDN博客可以使用同样的方法来选中物体的某一个表面。这一节在Pi…...

open ai chartgpt 安装插件 txyz.ai

1 chatgpt 页面 左下角 用户 -> setting 2 3...

【算法思想】贪心

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kuan 的首页,持续学…...

freeswitch-01

文章目录 1. 电话实现技术2. 模拟信号与数字信号2.1 模拟信号2.2 数字信号 3. PCM4. 局间中继与电路复用技术5. 信令5.1 定义5.2 分类5.2.1 功能分类5.2.2 工作区域分类5.2.3 信道分类 5.3 用户线信令5.4 局间信令5.5 七号信令5.6 H.323与SIP信令 6. 媒体6.1 定义 7. 电路交换与…...

Zookeeper-集群介绍与核心理论

Zookeeper集群 4.Zookeeper集群4.1) 介绍4.2) 核心理论 4.Zookeeper集群 4.1) 介绍 Leader选举&#xff1a; Serverid&#xff1a;服务器ID。比如有三台服务器&#xff0c;编号分别是1,2,3。编号越大在选择算法中的权重越大。Zxid&#xff1a;数据ID。服务器中存放的最大数据…...

动态分配的内存位置在哪里?

在C++中,动态分配的内存位于称为堆(Heap)的内存区域。以下是一些关于堆和其他相关内存区域的基本信息: 堆(Heap): 这是一个用于动态内存分配的内存区域。使用new(C++)或malloc(C)等函数从堆中分配内存,并使用delete(C++)或free(C)释放这些内存。堆的大小通常受…...

Vue3中的Ref与Reactive:深入理解响应式编程

前言 Vue 3是一个功能强大的前端框架&#xff0c;它引入了一些令人兴奋的新特性&#xff0c;其中最引人注目的是ref和reactive。这两个API是Vue 3中响应式编程的核心&#xff0c;本文将深入探讨它们的用法和差异。 什么是响应式编程&#xff1f; 在Vue中&#xff0c;响应式编…...

Windows10/11显示文件扩展名 修改文件后缀名教程

前言 写这篇文章的原因是由于我分享的教程中的文件、安装包基本都是存在阿里云盘的&#xff0c;下载后需要改后缀名才能使用。 但是好多同学不会改。。 Windows 10 随便打开一个文件夹&#xff0c;在上方工具栏点击 “查看”点击 “查看” 后下方会显示更详细的工具栏然后点…...

【C++】手撕string(string的模拟实现)

手撕string目录&#xff1a; 一、 Member functions 1.1 constructor 1.2 Copy constructor&#xff08;代码重构&#xff1a;传统写法和现代写法&#xff09; 1.3 operator&#xff08;代码重构&#xff1a;现代写法超级牛逼&#xff09; 1.4 destructor 二、Other mem…...

用python3编译cv_bridge

文章目录 概要依赖工作空间编译可能遇到的问题error: option --install-layout not recognized概要 当我在编写一个使用传感器图像传输和OpenCV4的ROS包时,从构建到编译代码的一切都很顺利。当我开始运行节点本身时,问题出现了,它给出了以下错误: Assertion failed (tlsSl…...

招商信诺人寿基于 Apache Doris 统一 OLAP 技术栈实践

本文导读&#xff1a; 当前&#xff0c;大数据、人工智能、云计算等技术应用正在推动保险科技发展&#xff0c;加速保险行业数字化进程。在这一背景下&#xff0c;招商信诺不断探索如何将多元数据融合扩充&#xff0c;以赋能代理人掌握更加详实的用户线索&#xff0c;并将智能…...

我的python安装在哪儿了?python安装路径怎么查?

对于 Python 开发者来说&#xff0c;Windows 系统中的 Python 安装路径是非常重要的。在本文中&#xff0c;我们将从多个方面探究如何查看 Python 安装路径&#xff0c;并提供代码示例。 一、使用文件浏览器查看 Python 安装路径 在 Windows 系统中&#xff0c;我们可以使用文…...

视频汇聚/安防监控平台EasyCVR指定到新的硬盘进行存储录像,如何自动挂载该磁盘?

TSINGSEE青犀视频监控汇聚平台EasyCVR可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安防视频监控的能力&…...

读博时的建议或心得

https://www.zhihu.com/question/32210068/answer/264273093 读论文&#xff1a;一开始读论文&#xff0c;一定要读顶会顶刊的&#xff0c;以后也一直要这样。如此&#xff0c;一方面保持了研究的水准&#xff0c;时刻提醒自己&#xff1a;我就是混这个层次的。另一方面&#…...

3分钟,免费制作一个炫酷实用的数据可视化大屏!

在当前大数据时代背景下&#xff0c;数据已成为在工业革命中如同煤炭、石油一般宝贵的资源。但是由于数据越来越庞大、越来越复杂&#xff0c;导致数据的可读性也越来越低。因此&#xff0c;对数据可视化的需求也越来越高&#xff0c;需要解决的问题也越来越复杂&#xff0c;而…...

自费访学|金融公司高管赴世界名校伯克利交流

R经理决定抽出一年时间&#xff0c;自费赴美国访学&#xff0c;向国外导师请教&#xff0c;探讨了解不同社会环境下&#xff0c;各种经济及社会现象的产生和发展&#xff0c;在思维碰撞中提升自身的国际视野。最终我们为其联系到世界名校-加州大学伯克利分校&#xff0c;导师为…...

Databend 开源周报第112期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 理解用户自定义…...

如何学习maya mel语言的经验分享

一、前言 总结一下这十几年来学习和使用mel语言的一些经验&#xff0c;供初学朋参考&#xff0c;哈哈。 这里不说深奥理论&#xff0c;只是朴实经历陈述。 其实&#xff0c;早在2003年&#xff0c;最初接触maya时&#xff0c;就已经涉及到mel的学习&#xff0c;当时在大学里接…...

睿趣科技:新手抖音开店卖什么产品好

抖音已经成为了一款年轻人热爱的社交媒体应用&#xff0c;同时也成为了一种全新的电商平台。对于新手来说&#xff0c;抖音开店卖什么产品是一个备受关注的问题。在这篇文章中&#xff0c;我们将探讨一些适合新手的产品选择&#xff0c;帮助他们在抖音上开店获得成功。 流行时尚…...

【新版】系统架构设计师 - 案例分析 - 架构设计<Web架构>

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 文章目录 架构 - 案例分析 - 架构设计&#xff1c;Web架构&#xff1e;Web架构知识点单台机器 到 数据库与Web服务器分离应用服务器集群负载均衡负载均衡技术静态与动态算法Session共享机制有状态与无状态 持久化技…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查

在对接支付宝API的时候&#xff0c;遇到了一些问题&#xff0c;记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战

在现代战争中&#xff0c;电磁频谱已成为继陆、海、空、天之后的 “第五维战场”&#xff0c;雷达作为电磁频谱领域的关键装备&#xff0c;其干扰与抗干扰能力的较量&#xff0c;直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器&#xff0c;凭借数字射…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...

Golang——9、反射和文件操作

反射和文件操作 1、反射1.1、reflect.TypeOf()获取任意值的类型对象1.2、reflect.ValueOf()1.3、结构体反射 2、文件操作2.1、os.Open()打开文件2.2、方式一&#xff1a;使用Read()读取文件2.3、方式二&#xff1a;bufio读取文件2.4、方式三&#xff1a;os.ReadFile读取2.5、写…...

第7篇:中间件全链路监控与 SQL 性能分析实践

7.1 章节导读 在构建数据库中间件的过程中&#xff0c;可观测性 和 性能分析 是保障系统稳定性与可维护性的核心能力。 特别是在复杂分布式场景中&#xff0c;必须做到&#xff1a; &#x1f50d; 追踪每一条 SQL 的生命周期&#xff08;从入口到数据库执行&#xff09;&#…...

关于easyexcel动态下拉选问题处理

前些日子突然碰到一个问题&#xff0c;说是客户的导入文件模版想支持部分导入内容的下拉选&#xff0c;于是我就找了easyexcel官网寻找解决方案&#xff0c;并没有找到合适的方案&#xff0c;没办法只能自己动手并分享出来&#xff0c;针对Java生成Excel下拉菜单时因选项过多导…...