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 latitude
  magXY = Math.sqrt(
    spherePoint.x * spherePoint.x + spherePoint.y * spherePoint.y
  );

  // Use fastApproximateAtan2 for alignment with shader
  sphereLatitude = 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 analysis
    if (
      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);
    // 计算uv
    float u = (float(pixX) + 0.5) * pixelWidth; // sample from center of pixel
    float 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,和上一个坐标b
                vec2 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.mfbz.cn/a/716878.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Spring框架永远滴神之SpringAI玩转大模型

文章目录 一、SpringAI简介1.什么是SpringAI2.SpringAI支持的大模型类型&#xff08;1&#xff09;聊天模型&#xff08;2&#xff09;文本到图像模型&#xff08;3&#xff09;转录&#xff08;音频到文本&#xff09;模型&#xff08;4&#xff09;嵌入模型&#xff08;5&…

多标签识别:JoyTag模型的图像标注革命【开源】

公共视觉模型通常会对其训练数据集进行严格过滤&#xff0c;这限制了这些基础模型在广泛概念上的表现&#xff0c;进而限制了表达自由、包容性和多样性。JoyTag通过结合Danbooru 2021数据集和一组手动标记的图像&#xff0c;努力提高模型对不同类型图像的泛化能力。 JoyTag项目…

Python批量保存Excel文件中的图表为图片

Excel工作簿作为一款功能强大的数据处理与分析工具&#xff0c;被广泛应用于各种领域&#xff0c;不仅能够方便地组织和计算数据&#xff0c;还支持用户创建丰富多彩的图表&#xff0c;直观展示数据背后的洞察与趋势。然而&#xff0c;在报告编制、网页内容制作或分享数据分析成…

新办理北京广播电视节目制作许可证需要什么条件

在北京想要从事广播电视节目制作&#xff0c;那就需要企业拥有广播电视节目制作经营许可证。此许可证不仅是企业合法经营的基础&#xff0c;同时也是保障节目制作质量和内容合规的标志。如何办理&#xff0c;详情致电咨询我或者来公司面谈。 北京广播电视节目制作经营许可证申请…

开源项目大合集(热门)

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

【Python】PySide6使用入门和注意事项

文章目录 前言关于PySide和PyQtQt Designerpyside6在vscode中ui文件转换兼容性问题主程序结构蓝牙协议初探&#xff08;应用层&#xff09; 前言 最近在开发一个带界面的软件&#xff0c;需要使用蓝牙&#xff0c;然后找到一个开源仓库使用的是Qt里面的Qbluetooth模块&#xff…

「网络原理」IP 协议

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;计网 &#x1f387;欢迎点赞收藏加关注哦&#xff01; IP 协议 &#x1f349;报头结构&#x1f349;地址管理&#x1f34c;动态分配 IP 地址&#x1f34c;NAT 机制&#xff08;网络地址映射&am…

AMD平台,5600X+6650XT,虚拟机安装macOS 14(2024年6月)

AMD平台安装macOS 14的麻烦&#xff0c;要比Intel平台多的多&#xff0c;由于macOS从13开始&#xff0c;对CPU寄存器的读取进行了改变&#xff0c;导致AMD平台只要安装完macOS 13及以后版本&#xff0c;开机后就报五国语言错误&#xff0c;不断重启。改vmx文件&#xff0c;被证…

VR虚拟仿真技术模拟还原给水厂内外部结构

在厂区的外围&#xff0c;我们采用VR全景拍摄加3D开发建模的方式&#xff0c;还原了每一处细节&#xff0c;让你仿佛置身于现场&#xff0c;感受那份宁静与庄重。 当你踏入厂区&#xff0c;我们为你精心策划了一条游览路线&#xff0c;从门口到各个重要场景&#xff0c;一一为…

2025年计算机毕业设计题目参考

今年最新计算机毕业设计题目参考 以下可以参考 springboot洗衣店订单管理系统 springboot美发门店管理系统 springboot课程答疑系统 springboot师生共评的作业管理系统 springboot平台的医疗病历交互系统 springboot购物推荐网站的设计与实现 springboot知识管理系统 springbo…

Pytorch深度解析:Transformer嵌入层源码逐行解读

前言 本部分博客需要先阅读博客&#xff1a; 《Transformer实现以及Pytorch源码解读&#xff08;一&#xff09;-数据输入篇》 作为知识储备。 Embedding使用方式 如下面的代码中所示&#xff0c;embedding一般是先实例化nn.Embedding(vocab_size, embedding_dim)。实例化的…

怎么给二维码添加文字或logo?快速美化二维码的使用技巧

怎么给已生成的二维码修改样式呢&#xff1f;目前常规生成的二维码大多是普通黑白色的&#xff0c;没有明显的标识不利于用户辨别。想要提升二维码的辨识度可以通过添加logo、添加文字的方式来改变二维码的样式&#xff0c;让用户看到二维码就知道是否是自己需要的内容&#xf…

智能制造uwb高精度定位系统模块,飞睿智能3厘米定位测距芯片,无人机高速传输

在科技日新月异的今天&#xff0c;定位技术已经渗透到我们生活的方方面面。从手机导航到自动驾驶&#xff0c;再到无人机定位&#xff0c;都离不开精准的定位系统。然而&#xff0c;随着应用场景的不断拓展&#xff0c;传统的定位技术如GPS、WiFi定位等&#xff0c;因其定位精度…

【AI基础】大模型部署工具之ollama的安装部署

ollama是大模型部署方案&#xff0c;对应docker&#xff0c;本质也是基于docker的容器化技术。 从前面的文章可以看到&#xff0c;部署大模型做的准备工作是比较繁琐的&#xff0c;包括各个环节的版本对应。ollama提供了一个很好的解决方案。 ollama主要针对主流的LLaMA架构的…

如何使用xurlfind3r查找目标域名的已知URL地址

关于xurlfind3r xurlfind3r是一款功能强大的URL地址查询工具&#xff0c;该工具本质上是一个CLI命令行工具&#xff0c;可以帮助广大研究人员从多种在线源来查询目标域名的已知URL地址。 功能介绍 1、从被动在线源获取URL地址以实现最大数量结果获取&#xff1b; 2、支持从Way…

可通过小球进行旋转的十字光标(vtkResliceCursor)

前一段事件看到VTK的一个例子&#xff1a; 该案例是vtk.js写的&#xff0c;觉得很有意思&#xff0c;个人正好也要用到&#xff0c;于是萌生了用C修改VTK源码来实现该功能的想法。原本以为很简单&#xff0c;只需要修改一下vtkResliceCursor就可以了&#xff0c;加上小球&#…

【面试 - 页面优化举例】页面跳转卡顿问题解决 - 页面跳转速度优化

目录 为何要优化如何优化优化1 - 懒加载优化2 - el-tree 子节点默认不展开 为何要优化 页面A跳转到也页面B时&#xff0c;页面出现卡顿情况&#xff1a; 【问题】页面A → 页面B时&#xff0c;页面B进入到了 created 钩子后过了六七秒才进入到 mounted 钩子&#xff1b;【分析经…

遗传算法浅理解

1. 什么是遗传算法&#xff1f; ​ 遗传算法&#xff0c;又称为 Genetic algorithm(GA)Genetic algorithm(GA)。其主要思想就是模拟生物的遗传与变异。它的用途非常广泛&#xff0c;可以用于加速某些求最大或者最小值的算法&#xff08;换句话说就是加速算法收敛&#xff0c;最…

PV180R1K1T1NMMC派克通轴传动结构柱塞泵

PV180R1K1T1NMMC派克通轴传动结构柱塞泵 派克柱塞泵的结构组成部分&#xff1a;柱塞、手把、斜盘、压盘、滑履、泵体、配油盘、传送轴。其优点如下&#xff1a; 1、结构紧凑耐用&#xff0c;具有灵活的安装接口 2、安静的工作 3、效率高 4、降低功耗和减少发热 5、具有“…

升级到tomcat10和Java 21后,idea控制台system.out.println输出中文乱码问题

最近一次性从tomcat 9升级到tomcat 10&#xff0c;同时Java sdk也从1.8升级到21。 升级过程中&#xff0c;当然会遇到很多问题&#xff0c;但是控制台输出中文乱码问题&#xff0c;着实折腾了很久。 1、尝试各种方法 网上说的很多通用方法都试过了&#xff0c;就是不生效。包…