cesium ClippingPolygon多边形裁切

1.多边形裁切

1.1 基本流程

        cesium117版本添加了多边形裁切功能,本文分析源码,看看是如何处理的。多边形裁切的大概流程分为4部分:

  1. 通过经纬度坐标传入多个闭合的边界;
  2. 将多个边界打包成两张纹理,一张是每个多边形的坐标,另一张是每个多边形的边界;
  3. 将两张多边形纹理通过一个计算着色器(屏幕空间着色器模拟计算着色器)生成一张符号距离场纹理;
  4. 将这两张图传入地球瓦片和3DTiles的着色器中进行多边形裁切.

1.2 多边形纹理打包

        这是在js代码中处理的,使用了ClippingPolygon和ClippingPolygonCollection两个类,ClippingPolygon类负责每个多边形的坐标收集以及每个多边形的范围计算。

            以下是ClippingPolygon类的主要代码,过程比较简单。

/*** Computes a rectangle with the spherical extents that encloses the polygon defined by the list of positions, including cases over the international date line and the poles.* 根据给定的位置列表计算球上的坐标区域(使用弧度表示),包括越过国际日期线和极点的情况* @private** @param {Rectangle} [result] An object in which to store the result.* @returns {Rectangle} The result rectangle with spherical extents.*/
ClippingPolygon.prototype.computeSphericalExtents = function (result) {if (!defined(result)) {result = new Rectangle();}// 经纬度范围const rectangle = this.computeRectangle(scratchRectangle);// 计算出球面点笛卡尔let spherePoint = Cartographic.toCartesian(Rectangle.southwest(rectangle),this.ellipsoid,spherePointScratch);// Project into plane with vertical for latitude// 投影到具有垂直纬度的平面中let magXY = Math.sqrt(spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y);// Use fastApproximateAtan2 for alignment with shader// 球面纬度let sphereLatitude = CesiumMath.fastApproximateAtan2(magXY, spherePoint.z);// 球面经度let sphereLongitude = CesiumMath.fastApproximateAtan2(spherePoint.x,spherePoint.y);// 西南的经纬度result.south = sphereLatitude;result.west = sphereLongitude;// 计算东北点位spherePoint = Cartographic.toCartesian(Rectangle.northeast(rectangle),this.ellipsoid,spherePointScratch);// Project into plane with vertical for latitudemagXY = Math.sqrt(spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y);// Use fastApproximateAtan2 for alignment with shadersphereLatitude = CesiumMath.fastApproximateAtan2(magXY, spherePoint.z);sphereLongitude = CesiumMath.fastApproximateAtan2(spherePoint.x,spherePoint.y);// 计算东北经纬度result.north = sphereLatitude;result.east = sphereLongitude;return result;
};

        ClippingPolygonCollection类的过程主要在update函数中,函数过程如下

ClippingPolygonCollection.prototype.update = function (frameState) {const context = frameState.context;// 是否支持if (!ClippingPolygonCollection.isSupported(frameState)) {throw new RuntimeError("ClippingPolygonCollections are only supported for WebGL 2.");}// It'd be expensive to validate any individual position has changed. Instead verify if the list of polygon positions has had elements added or removed, which should be good enough for most cases.// 验证任何个人立场的改变都是昂贵的。相反,请验证多边形位置列表中是否添加或删除了元素,这在大多数情况下应该足够好。// 总共的顶点数量const totalPositions = this._polygons.reduce((totalPositions, polygon) => totalPositions + polygon.length,0);// 总共的顶点数量不变if (totalPositions === this.totalPositions) {return;}this._totalPositions = totalPositions;// If there are no clipping polygons, there's nothing to update.if (this.length === 0) {return;}// 符号距离计算命令,命令存在就取消if (defined(this._signedDistanceComputeCommand)) {// 如果正在计算就取消this._signedDistanceComputeCommand.canceled = true;this._signedDistanceComputeCommand = undefined;}// 多边形纹理let polygonsTexture = this._polygonsTexture;// 范围纹理let extentsTexture = this._extentsTexture;// 符号距离纹理let signedDistanceTexture = this._signedDistanceTexture;if (defined(polygonsTexture)) {// 当前像素数量const currentPixelCount = polygonsTexture.width * polygonsTexture.height;// Recreate the texture to double current requirement if it isn't big enough or is 4 times larger than it needs to be.// Optimization note: this isn't exactly the classic resizeable array algorithm// * not necessarily checking for resize after each add/remove operation// * random-access deletes instead of just pops// * alloc ops likely more expensive than demonstrable via big-O analysis/*重建2倍的当前纹理,如果不够到,或者是所需内存的4倍,优化注意:这不是经典的重新设置数组大小的算法,不一定要在每次添加/删除操作后检查是否调整大小随机访问删除而不是弹出分配操作可能比通过big-O(大O分析法)分析证明的更昂贵*/if (currentPixelCount < this.pixelsNeededForPolygonPositions ||       // 内存不够大this.pixelsNeededForPolygonPositions < 0.25 * currentPixelCount   // 所需要的比当前四分之一还小,就需要重新分配显存) {// 销毁纹理polygonsTexture.destroy();polygonsTexture = undefined;this._polygonsTexture = undefined;}}if (!defined(polygonsTexture)) {// 获取分辨率const requiredResolution = ClippingPolygonCollection.getTextureResolution(polygonsTexture,this.pixelsNeededForPolygonPositions,textureResolutionScratch);// 创建纹理polygonsTexture = new Texture({context: context,width: requiredResolution.x,height: requiredResolution.y,pixelFormat: PixelFormat.RG,pixelDatatype: PixelDatatype.FLOAT,sampler: Sampler.NEAREST,flipY: false,});// 数据this._float32View = new Float32Array(requiredResolution.x * requiredResolution.y * 2);// 纹理this._polygonsTexture = polygonsTexture;}// 处理范围纹理if (defined(extentsTexture)) {const currentPixelCount = extentsTexture.width * extentsTexture.height;// Recreate the texture to double current requirement if it isn't big enough or is 4 times larger than it needs to be.// Optimization note: this isn't exactly the classic resizeable array algorithm// * not necessarily checking for resize after each add/remove operation// * random-access deletes instead of just pops// * alloc ops likely more expensive than demonstrable via big-O analysisif (currentPixelCount < this.pixelsNeededForExtents ||this.pixelsNeededForExtents < 0.25 * currentPixelCount) {extentsTexture.destroy();extentsTexture = undefined;this._extentsTexture = undefined;}}if (!defined(extentsTexture)) {// 获取范围纹理的分辨率const requiredResolution = ClippingPolygonCollection.getTextureResolution(extentsTexture,this.pixelsNeededForExtents,textureResolutionScratch);// 创建范围纹理extentsTexture = new Texture({context: context,width: requiredResolution.x,height: requiredResolution.y,pixelFormat: PixelFormat.RGBA,pixelDatatype: PixelDatatype.FLOAT,sampler: Sampler.NEAREST,flipY: false,});// 范围纹理依赖的数据内存this._extentsFloat32View = new Float32Array(requiredResolution.x * requiredResolution.y * 4);this._extentsTexture = extentsTexture;}// 打包多边形packPolygonsAsFloats(this);// 拷贝范围的纹理数据extentsTexture.copyFrom({source: {width: extentsTexture.width,height: extentsTexture.height,arrayBufferView: this._extentsFloat32View,},});// 拷贝多边形纹理数据polygonsTexture.copyFrom({source: {width: polygonsTexture.width,height: polygonsTexture.height,arrayBufferView: this._float32View,},});// 定义符号距离场景if (!defined(signedDistanceTexture)) {// 符号距离场纹理分辨率const textureDimensions = ClippingPolygonCollection.getClippingDistanceTextureResolution(this,textureResolutionScratch);// 符号距离纹理signedDistanceTexture = new Texture({context: context,width: textureDimensions.x,height: textureDimensions.y,pixelFormat: context.webgl2 ? PixelFormat.RED : PixelFormat.LUMINANCE,  // 只有一个通道pixelDatatype: PixelDatatype.FLOAT,sampler: new Sampler({wrapS: TextureWrap.CLAMP_TO_EDGE,wrapT: TextureWrap.CLAMP_TO_EDGE,minificationFilter: TextureMinificationFilter.LINEAR,magnificationFilter: TextureMagnificationFilter.LINEAR,}),flipY: false,});this._signedDistanceTexture = signedDistanceTexture;}// 创建符号距离场命令this._signedDistanceComputeCommand = createSignedDistanceTextureCommand(this);
};

        这个过程中主要是如很将多边形信息打包到两个纹理中,以及创建一张距离场纹理,用于后续将计算着色器(像素着色器模拟计算着色器)的计算结果存入距离场纹理中。

        打包的两张纹理的结构图如下:

1.3 计算命令

        接着就是计算命令的创建过程:

// 创建距离场纹理命令
function createSignedDistanceTextureCommand(collection) {// 多边形纹理、范围纹理const polygonTexture = collection._polygonsTexture;const extentsTexture = collection._extentsTexture;// 计算命令return new ComputeCommand({fragmentShaderSource: PolygonSignedDistanceFS,    // 只有光栅化过程outputTexture: collection._signedDistanceTexture,  // 输出纹理uniformMap: {u_polygonsLength: function () { // 多少个多边形return collection.length;},u_extentsLength: function () {  // 多少个范围return collection.extentsCount;},u_extentsTexture: function () { // 范围纹理return extentsTexture;},u_polygonTexture: function () { // 多边形纹理return polygonTexture;},},persists: false,      // 持续使用这个命令,还是使用一次就释放owner: collection,    // 归属postExecute: () => {  // 执行完成后collection._signedDistanceComputeCommand = undefined;},});
}

        这个过程涉及到了ComputeCommand和ComputeEngine类,ComputeCommand类主要是收集信息,ComputeEngine类主要是update函数

// 执行
ComputeEngine.prototype.execute = function (computeCommand) {//>>includeStart('debug', pragmas.debug);Check.defined("computeCommand", computeCommand);//>>includeEnd('debug');// This may modify the command's resources, so do error checking afterwards// 可能会更改命令的分辨率,后续会做错误检查if (defined(computeCommand.preExecute)) {computeCommand.preExecute(computeCommand);}//>>includeStart('debug', pragmas.debug);if (!defined(computeCommand.fragmentShaderSource) &&!defined(computeCommand.shaderProgram)) {throw new DeveloperError("computeCommand.fragmentShaderSource or computeCommand.shaderProgram is required.");}Check.defined("computeCommand.outputTexture", computeCommand.outputTexture);//>>includeEnd('debug');// 输出的纹理const outputTexture = computeCommand.outputTexture;const width = outputTexture.width;const height = outputTexture.height;const context = this._context;// 定义顶点数组const vertexArray = defined(computeCommand.vertexArray)? computeCommand.vertexArray: context.getViewportQuadVertexArray();  // 获取视口四边形顶点// 着色程序const shaderProgram = defined(computeCommand.shaderProgram)? computeCommand.shaderProgram: createViewportQuadShader(context, computeCommand.fragmentShaderSource);  // 创建视口着色器// 创建帧缓冲const framebuffer = createFramebuffer(context, outputTexture);// 创建渲染状态const renderState = createRenderState(width, height);const uniformMap = computeCommand.uniformMap;// 执行清空命令const clearCommand = clearCommandScratch;clearCommand.framebuffer = framebuffer;clearCommand.renderState = renderState;clearCommand.execute(context);// 执行绘制命令const drawCommand = drawCommandScratch;drawCommand.vertexArray = vertexArray;drawCommand.renderState = renderState;drawCommand.shaderProgram = shaderProgram;drawCommand.uniformMap = uniformMap;drawCommand.framebuffer = framebuffer;drawCommand.execute(context);// 执行完成销毁framebuffer.destroy();// 非持久的计算命令(一次性的)if (!computeCommand.persists) {shaderProgram.destroy();if (defined(computeCommand.vertexArray)) {vertexArray.destroy();}}// 处理完成后的回调if (defined(computeCommand.postExecute)) {computeCommand.postExecute(outputTexture);}
};

类似一个后处理过程,创建一个四边形,占满整个屏幕,然后使用像素着色器进行距离场插值计算。

1.4 生成距离场纹理

        这个过程是在着色器中处理的,PolygonSignedDistanceFS.glsl文件中是计算过程

in vec2 v_textureCoordinates;uniform int u_polygonsLength;
uniform int u_extentsLength;
uniform highp sampler2D u_polygonTexture;
uniform highp sampler2D u_extentsTexture;// 获取多边形索引
int getPolygonIndex(float dimension, vec2 coord) {// 将当前的纹理坐标(按照0~1的比例)转换到(范围纹理的)整数坐标vec2 uv = coord.xy * dimension;return int(floor(uv.y) * dimension + floor(uv.x));
}// 获取范围
vec2 getLookupUv(ivec2 dimensions, int i) {//int pixY = i / dimensions.x;int pixX = i - (pixY * dimensions.x);// 获取宽度、高度步长float pixelWidth = 1.0 / float(dimensions.x);float pixelHeight = 1.0 / float(dimensions.y);// 计算uvfloat u = (float(pixX) + 0.5) * pixelWidth; // sample from center of pixelfloat v = (float(pixY) + 0.5) * pixelHeight;return vec2(u, v);
}// 获取范围
vec4 getExtents(int i) {return texture(u_extentsTexture, getLookupUv(textureSize(u_extentsTexture, 0), i));
}//
ivec2 getPositionsLengthAndExtentsIndex(int i) {//vec2 uv = getLookupUv(textureSize(u_polygonTexture, 0), i);vec4 value = texture(u_polygonTexture, uv);return ivec2(int(value.x), int(value.y));
}vec2 getPolygonPosition(int i) {vec2 uv = getLookupUv(textureSize(u_polygonTexture, 0), i);return texture(u_polygonTexture, uv).xy;
}vec2 getCoordinates(vec2 textureCoordinates, vec4 extents) {// 插值出中间坐标 extents.x:范围开始的地方,extents.x + 1.0 / extents.z:范围结束的地方float latitude = mix(extents.x, extents.x + 1.0 / extents.z, textureCoordinates.y);float longitude = mix(extents.y, extents.y + 1.0 / extents.w, textureCoordinates.x);return vec2(latitude, longitude);
}/*
具体的逻辑好像是:
如果是4个范围,则将整个4096*4096的图像分成四部分,每一个部分进行距离场计算,如果是8个范围,则就缩小每个距离范围的分辨率,
*/
void main() {int lastPolygonIndex = 0;out_FragColor = vec4(1.0);// Get the relevant region of the texture 获取纹理的相关区域// 范围个数,例如100个float dimension = float(u_extentsLength);// 多于2个范围if (u_extentsLength > 2) {//转化成一个正方形的范围dimension = ceil(log2(float(u_extentsLength)));}// 坐标转换成索引(这个像素gl_FragCoord)对应的范围索引int regionIndex = getPolygonIndex(dimension, v_textureCoordinates);// 遍历多边形for (int polygonIndex = 0; polygonIndex < u_polygonsLength; polygonIndex++) {// 获取每一个多边形的顶点个数和这个多边形的范围索引ivec2 positionsLengthAndExtents = getPositionsLengthAndExtentsIndex(lastPolygonIndex);// 长度int positionsLength = positionsLengthAndExtents.x;// 索引int polygonExtentsIndex = positionsLengthAndExtents.y;lastPolygonIndex += 1;// Only compute signed distance for the relevant part of the atlas// 仅计算图集相关部分的有符号距离// 找到对应的区域if (polygonExtentsIndex == regionIndex) {float clipAmount = czm_infinity;// 这个多边形对应的范围vec4 extents = getExtents(polygonExtentsIndex);// 偏移,将范围左边转换到一个正方形的范围内vec2 textureOffset = vec2(mod(float(polygonExtentsIndex), dimension), floor(float(polygonExtentsIndex) / dimension)) / dimension;// 插值出的坐标vec2 p = getCoordinates((v_textureCoordinates - textureOffset) * dimension, extents);float s = 1.0;// Check each edge for absolute distance 绝对距离检查每个边// 这个多边形的遍历坐标for (int i = 0, j = positionsLength - 1; i < positionsLength; j = i, i++) {// 获取多边形的坐标a,和上一个坐标bvec2 a = getPolygonPosition(lastPolygonIndex + i);vec2 b = getPolygonPosition(lastPolygonIndex + j);// 两个点(经纬度点)之间的差vec2 ab = b - a;//vec2 pa = p - a;// 直线pa在直线ab上的投影(在单位直线ab)// pa在ab单位向量上的投影,然后在除以ab,即占pa总长度的百分比float t = dot(pa, ab) / dot(ab, ab);// 百分比限制在【0.0~1.0】之间t = clamp(t, 0.0, 1.0);// 计算垂线vec2 pq = pa - t * ab;// 计算垂线距离float d = length(pq);// Inside / outside computation to determine sign// 内外计算决定符号bvec3 cond = bvec3(p.y >= a.y,p.y < b.y,ab.x * pa.y > ab.y * pa.x);if (all(cond) || all(not(cond))) s = -s;// 找到距离最小的一个if (abs(d) < abs(clipAmount)) {// 裁切数量(有向距离场的垂线)clipAmount = d;}}// Normalize the range to [0,1] 归一化范围到【0-1】// clipAmount * length(extents.zw)转换到【-1~1】,然后添加s符号,然后/2为转换到【-0.5~0.5】,然后+0.5为转换到【0~1】vec4 result = (s * vec4(clipAmount * length(extents.zw))) / 2.0 + 0.5;// In the case where we've iterated through multiple polygons, take the minimum// 在我们迭代多个多边形的情况下,取最小值out_FragColor = min(out_FragColor, result);}lastPolygonIndex += positionsLength;}
}

        上述过程有点绕,主要过程如下:

  1.  根据光栅化插值的特点将每个像素转换到对应的范围纹理坐标中,这个坐标是一个索引;
  2. 根据上述索引,遍历多边形纹理中的数据,看看那个多边形的范围索引与1中计算出来的索引对应;
  3. 遍历这个索引下的多边形中的相邻的两个顶点坐标并计算向量,然后再计算像素坐标对应的边界插值坐标,将插值坐标投影到计算向量上,然后计算垂向量,垂向量的长度就是距离场;
  4. 当前像素点对应的坐标距离最短的那个边界的长度,然后计算符号,最后存入纹理中。

        上面glsl的过程中,原来的范围长度是一维数组,经过如下计算会转换为边长为dimension的正方形,对应于上图四个图中的一个。

// Get the relevant region of the texture 获取纹理的相关区域// 范围个数,例如100个float dimension = float(u_extentsLength);// 多于2个范围if (u_extentsLength > 2) {//转化成一个正方形的范围dimension = ceil(log2(float(u_extentsLength)));}

          由于uv坐标是【0~1】范围内的,所以需要将uv坐标 转换成externsTexture纹理的像素坐标,计算这是第几个范围,引文一个纹素对应者一个范围,第几个纹素就是第几个范围。

// 获取多边形索引
int getPolygonIndex(float dimension, vec2 coord) {// 将当前的纹理坐标(按照0~1的比例)转换到(范围纹理的)整数坐标vec2 uv = coord.xy * dimension;return int(floor(uv.y) * dimension + floor(uv.x));
}

        例如:dimension是2x2的4个像素,而coord是【0~1】的范围,假设是coord=(0.6, 0.6)则计算出来就是coord*2 =(1.2, 1.2)取整数就是(1,1),就是这个像素,所以(0,0)到(0.5,0.0)范围对应第一行第一列的像素,所以(0.5,0)到(1.0,0.0)范围对应第一行第二列的像素,(0.0,0.5)到(0.0,1.0)范围对应第二行第一列的像素,所以(0.5,0.5)到(1.0,1.0)范围对应第二行第二列的像素。

        将整个4096x4096的距离场纹理划分成4个部分,每个部分就是一个polygon,然后按照如下

// 这个多边形对应的范围vec4 extents = getExtents(polygonExtentsIndex);// 偏移,将范围左边转换到一个正方形的范围内vec2 textureOffset = vec2(mod(float(polygonExtentsIndex), dimension), floor(float(polygonExtentsIndex) / dimension)) / dimension;// 插值出的坐标vec2 p = getCoordinates((v_textureCoordinates - textureOffset) * dimension, extents);

代码进行计算,索引找到就能查出范围externs,textureOffset为映射出的uv坐标,p就是映射出的uv坐标对应的经纬度坐标。

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

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

相关文章

语音识别相关文章整理目录

一、语音大模型架设与功能实现 使用sherpa-ncnn进行中文语音识别&#xff08;ubuntu22&#xff09;-CSDN博客文章浏览阅读953次&#xff0c;点赞30次&#xff0c;收藏26次。请注意&#xff0c;需要首先安装安装了所有必要的依赖项&#xff0c;包括 CMake、Git 和一个合适的 C/…

本地localhost与目标地址跨域问题的解决方法

场景 开发过程中遇到一个控件&#xff0c;上传图片到某cdn&#xff0c;目标地址对localhost会有跨域问题&#xff1a; 解决方法 参照此博客&#xff0c;将本地地址定义为某网址&#xff0c;如abc&#xff1a; win10修改本地host文件&#xff0c;用以增加自定义本地访问域名12…

装机后操作纪录

刚刚装完机 什么都没有 就像在一片一望无际的草原 要恢复原来笔记本的“秩序” 就像在这个草原建立全新的王国 1、关于显示器电脑屏幕图标巨大且糊的处理方法 用一台可正常使用的电脑&#xff0c;到主板官网下载相关驱动。(铭瑄B760M D4 WIFI驱动下载) 2、关于桌面没有显示“…

[Python学习篇] Python元组

元组&#xff08;Tuple&#xff09;&#xff1a;元组是不可变的&#xff0c;一旦创建就不能修改其内容。这意味着你不能增加、删除或更改元组中的元素。元组使用小括号()表示。元组可以一次性存储多个数据&#xff0c;且可以存不同数据类型。 定义元组 语法&#xff1a; # 存…

GitLab安装部署以及bug修复

使用git&#xff0c;还需要一个远程代码仓库。常见的github、gitee这种远程代码仓库&#xff0c;公司中一般不会使用&#xff0c;因为他们是使用外网的&#xff0c;不够安全。一般企业都会搭建一个仅内网使用的远程代码仓库&#xff0c;最常见就是 GitLab 安装准备 需要开启s…

从11个视角看全球Rust程序员1/4:深度解读JetBrains最新报告

讲动人的故事,写懂人的代码 五个月前,编程界的大佬JetBrains发布了他们的全球开发者年度报告。 小吾从这份报告中找出了下面11个关于全球程序员如何使用Rust的有趣的趋势,让你学习和使用Rust更轻松。 1 这两年有多少程序员在工作中使用了Rust? 2 全球程序员使用Rust有多…

设备保养计划不再是纸上谈兵,智能系统让执行更到位!

在物业管理的日常工作中&#xff0c;我们常常听到“设备保养台账”“设备保养计划”“设备保养记录”等等这些词&#xff0c;但你是否真正了解它们的含义&#xff1f;是否知道一个完善的设备保养计划、记录、台账对于物业运营的重要性&#xff1f;今天&#xff0c;我们就来深入…

3大法则教你高效制定奖励规则(含参考案例)

在实施全民分销的过程中&#xff0c;SaaS产品方和合作伙伴推广者之间的合作关系可以用河马与牛椋鸟之间的共生关系来形容——牛椋鸟以栖息在河马背上并清理其身上的昆虫为生。这种关系对两者来说都是极其有益的&#xff1a;牛椋鸟获得了稳定的食物来源&#xff0c;而河马则有效…

使用宝塔面板部署Django应用(不成功Kill Me!)

使用宝塔面板部署Django应用 文章目录 使用宝塔面板部署Django应用 本地操作宝塔面板部署可能部署失败的情况 本地操作 备份数据库 # 备份数据库 mysqldump -u root -p blog > blog.sql创建requirements # 创建requirements.txt pip freeze > requirements.txt将本项目…

梳理Y3游戏编辑器入门者需要明白的基础概念

前言 Y3编辑器是网易开发的一款类似于“War3地图编辑器”的产品。 最近KK对战平台上不少热门的RPG地图都出自Y3编辑器&#xff1a; 最近我花了些时间学习了这款编辑器的基础知识。我发现其中很多概念是比较抽象需要理解的&#xff0c;而有些概念比如“物件”、“物体”、“物…

二叉树-根据先序遍历和中序遍历序列重建二叉树

目录 一、问题描述 二、解题思路 1.首先明确先序遍历和中序遍历的性质&#xff1a; 2.确定根节点及左右子树 3.对子树进行递归操作 4.递归返回条件 三、代码实现 四、刷题链接 一、问题描述 二、解题思路 1.首先明确先序遍历和中序遍历的性质&#xff1a; 先序遍历&am…

Excel和Word等工具小技能分享汇编(一)

这里汇集刘小生前期微信公众号分享的Excel和Word等工具小技能&#xff0c;为方便大家查看学习&#xff0c;刘小生对其进行分类整理&#xff0c;后期也会不定期整理更新&#xff0c;如有想学习交流或其他小技巧需求&#xff0c;欢迎留言&#xff0c;我们一起学习进步&#xff01…

探索比特币多面体

目录 前言 一、比特币挖矿 1.挖矿设备的演化 2.矿池 二、比特币脚本 1.交易结构 2.交易的输入 3.交易的输出 4.P2PK 输入输出脚本的形式 实际执行情况 5.P2PKH 输入输出脚本的形式 实际执行情况 6.P2SH 输入输出脚本的形式 7.进一步说明 8.多重签名 9.脚本执…

DBA常用论坛

1.ITPUB ITPUB技术论坛_专业的IT技术社区 2.ASKTOM Ask TOM

Python 使用print输出二进制文件时产生的错位

项目实践中&#xff0c; with open(fileName, rb) as f: result f.read()print(result)f.close()打开二进制文件&#xff0c;打印出的结果会出现有些\x后面有好几个字符的情况 但实际这串数字是 这种情况是因为print函数将二进制数据解释为字符串并以其字节值的十六进制表…

Java中如何自定义异常进行抛出,并且定义全局异常处理类进行捕获异常(详细讲解)?

1.先理解为什么要抛出异常&#xff1f; 一句话就是为了终止程序&#xff0c;一般是终止业务层也就是service层。 2.为什么要自定义异常抛出&#xff1f; 因为系统提供的异常种类很多&#xff0c;而且代表的含义很多&#xff0c;所以我们需要自己定义一个通用的异常&#xff0…

白帽子最喜欢用什么渗透测试工具?看看哪些是你用过的

一、白帽子最喜欢用什么安全工具? 2020 年的 HackerOne 黑客报告中,统计过白帽子们最喜欢用的软硬件工具。 从图中可以看到,89% 的白帽子都会使用 Burp Suite 这个 Web 应用安全测试工具,有 39% 会尝试自己写工具,第三名的 Fuzzers 是模糊测试工具。再后面主要是一些代理…

STM32-17-DAC

STM32-01-认识单片机 STM32-02-基础知识 STM32-03-HAL库 STM32-04-时钟树 STM32-05-SYSTEM文件夹 STM32-06-GPIO STM32-07-外部中断 STM32-08-串口 STM32-09-IWDG和WWDG STM32-10-定时器 STM32-11-电容触摸按键 STM32-12-OLED模块 STM32-13-MPU STM32-14-FSMC_LCD STM32-15-DMA…

k8s学习--OpenKruise详细解释以及原地升级及全链路灰度发布方案

文章目录 OpenKruise简介OpenKruise来源OpenKruise是什么&#xff1f;核心组件有什么&#xff1f;有什么特性和优势&#xff1f;适用于什么场景&#xff1f; 什么是OpenKruise的原地升级原地升级的关键特性使用原地升级的组件原地升级的工作原理 应用环境一、OpenKruise部署1.安…

ssm宠物网站系统-计算机毕业设计源码07183

摘 要 在信息飞速发展的今天&#xff0c;网络已成为人们重要的信息交流平台。宠物网站每天都有大量的信息需要通过网络发布&#xff0c;为此&#xff0c;本人开发了一个基于B/S&#xff08;浏览器/服务器&#xff09;模式的宠物网站系统。 该系统以JJava编程语言、MySQL和SSM框…