Unity网格编程笔记[十]一些网格基础操作的封装(Mesh合并,UV映射,正反面反转,顶点合并,法线求切线计算等)

这里的代码是在 Unity网格编程笔记[五]网格切割
中整合出来的。

这里的mesh可以直接接入到使用mesh的unity组件

一些基础的属性还是要参考 Unity网格编程笔记[零]网格编程基础知识点

Mesh合并

网格的合并,其实底层也没那么复杂。对于三角面,只是顺序需要重新组织一下,其他的属性都是重新添加就可以了。

三角面的添加做法有点特别。

新添加的网格的三角面顺序不变。但是每个元素的顺序是在原来的三角面的基础上添加的。

这里个人认为应该是在顶点的顺序上添加,即用int length = mesh.vertexCount;替换下面的int length = triangles.Count;。经过实践发现可以做到切面正常缝合,切面的uv也没有问题。

个人认为这样改的原因是新添加的网格,他们的顶点顺序都是添加到了原网格的数组里,所以索引值都添加了mesh.vertexCount。那么他们对应的三角面的数组中的对应索引值应该也添加mesh.vertexCount。才能保证添加后的顶点索引能对应上顶点数组。

 public void Add(Mesh mesh)
        {
            for (int i = 0; i < mesh.vertexCount; i++)
            {
                vertices.Add(mesh.vertices[i]);
                uvs.Add(mesh.uv[i]);
                normals.Add(mesh.normals[i]);
                tangents.Add(mesh.tangents[i]);
            }
            int length = triangles.Count;
            //我替换后也能正确运行的做法 int length = mesh.vertexCount;
            for (int i = 0; i < mesh.triangles.Length; i++)
            {
                triangles.Add(mesh.triangles[i] + length);
            }
        }

顶点合并

这里的主要思想是将过于靠近并且属性几乎一致的顶点,去除掉,以免造成不必要的计算。

首先给定范围range,对于顶点和uv小于这个范围的纳入考虑范围。个人觉得这个范围分开来用数值表示比较好。

对于纳入范围的顶点,最后考虑他们的法线方向,当点积值等于1的时候,就认为法线方向相同。

这时进行顶点的删除

for (int k = 0; k < triangles.Count; k++)
{
     if (triangles[k] == j)
         triangles[k] = i;
     if (triangles[k] > j)
         triangles[k]--;
 }
 
 vertices.RemoveAt(j);
 normals.RemoveAt(j);
 tangents.RemoveAt(j);
 uvs.RemoveAt(j);

首先在triangles数组中到前后排序的两个属性相同位置相近的顶点中,将后面排序的顶点替换成前面的。然后替换后,对于遍历之后,位于该顶点在vertex数组之后的顶点,其索引值都减一。

减一的原因是在该顶点从vertex数组中被去除后,vertex数组位于该点后面的顶点在顶点序列中的存储的索引都会减少一位。而triangles中存储的就是顶点在vertex中的顶点索引。

public void CombineVertices(float range)
        {
            range *= range;
            for (int i = 0; i < vertices.Count; i++)
            {
                for (int j = i + 1; j < vertices.Count; j++)
                {
                    bool dis = (vertices[i] - vertices[j]).sqrMagnitude < range;
                    bool uv = (uvs[i] - uvs[j]).sqrMagnitude < range;
                    bool dir = Vector3.Dot(normals[i], normals[j]) > 0.999f;
                    if (dis && uv && dir)
                    {
                        for (int k = 0; k < triangles.Count; k++)
                        {
                            if (triangles[k] == j)
                                triangles[k] = i;
                            if (triangles[k] > j)
                                triangles[k]--;
                        }
                        vertices.RemoveAt(j);
                        normals.RemoveAt(j);
                        tangents.RemoveAt(j);
                        uvs.RemoveAt(j);
                    }
                }
            }
        }

面反转

  public void Reverse()
  {
      int count = triangles.Count / 3;
      for (int i = 0; i < count; i++)
      {
          int t = triangles[i * 3 + 2];
          triangles[i * 3 + 2] = triangles[i * 3 + 1];
          triangles[i * 3 + 1] = t;
      }
      count = vertices.Count;
      for (int i = 0; i < count; i++)
      {
          normals[i] *= -1;
          Vector4 tan = tangents[i];
          tan.w = -1;
          tangents[i] = tan;
      }
  }
  for (int i = 0; i < count; i++)
      {
          int t = triangles[i * 3 + 2];
          triangles[i * 3 + 2] = triangles[i * 3 + 1];
          triangles[i * 3 + 1] = t;
      }

这里的面反转,是将triangles数组的每三个顶点中的顺序的其中两个对调。这和面向相机的顶点是顺时针而反向相机的是逆时针的原因有关。

triangles中,每三个顶点是按照索引提升的顺序依次连接成面的。一般情况下,这样的自动连接是自动生成mesh的基础,所以连接顺序决定了面的法线朝向。法线永远是朝向图形学中的正面的。参考

正常情况下,在相机视角中,顶点的连接顺序从顺时针变成逆时针,或者从逆时针变成顺时针,那么这时面就翻转了。所以将三角数组triangles中的任意第二三个顶点调换顺序,后,再次按照索引升高的顺序连接三个顶点,一定会连接顺序变换。

Unity网格编程笔记[零]网格编程基础知识点

在连接顺序翻转之后,相关的属性也要相反,例如法线和切线

 for (int i = 0; i < count; i++)
      {
          normals[i] *= -1;
          Vector4 tan = tangents[i];
          tan.w = -1;
          tangents[i] = tan;
      }

法线直接乘以负一反向取反
但是切线的做法我回顾了 Unity学习shader笔记[三十一]关于顶点的法线、切线、副切线

在这里插入图片描述

才发现的,这里将翻转之前的面认为是正面了。准确一些的做法应该是

 tan.w = - tan.w ;

计算切线

 public static Vector4 CalculateTangent(Vector3 normal)
{
    Vector3 tan = Vector3.Cross(normal, Vector3.up);
    if (tan == Vector3.zero)
        tan = Vector3.Cross(normal, Vector3.forward);
    tan = Vector3.Cross(tan, normal);
    return new Vector4(tan.x, tan.y, tan.z, 1.0f);
}

这里计算切线,可以在世界坐标系或者局部坐标系下计算,结果都是一样的。判断叉乘结果是否等于零。原因是对于法线方向刚好正上方的顶点来说的,这时根据点积的原理,与正上方向量点积就是0.

那么此时该法线要与正前方做叉乘。其实根据法线求切线,求到的都是在一个平面空间下,即垂直于法线的平面空间,只要保证法线 切线 副切线两两垂直即可。所以这里直接用叉乘前或者上向量去求了。

最后再将此时求到的切线与法线叉乘,这是因为unity和maya中,切线空间是左手空间。切线 法线 副切线三者的分布与左手三指张开一样。
参考 Unity学习shader笔记[三十一]关于顶点的法线、切线、副切线
而叉乘的结果是右手定则,所以再次求叉乘,此时到切线与法线以及副切线的分布就与左手空间的三个手指一样了。

UV映射

 public void MapperCube(Rect range)
        {
            if (uvs.Count < vertices.Count)
                uvs = new List<Vector2>(vertices.Count);
            int count = triangles.Count / 3;
            for (int i = 0; i < count; i++)
            {
                int _i0 = triangles[i * 3];
                int _i1 = triangles[i * 3 + 1];
                int _i2 = triangles[i * 3 + 2];

                Vector3 v0 = vertices[_i0] - center + size / 2f;
                Vector3 v1 = vertices[_i1] - center + size / 2f;
                Vector3 v2 = vertices[_i2] - center + size / 2f;
                v0 = new Vector3(v0.x / size.x, v0.y / size.y, v0.z / size.z);
                v1 = new Vector3(v1.x / size.x, v1.y / size.y, v1.z / size.z);
                v2 = new Vector3(v2.x / size.x, v2.y / size.y, v2.z / size.z);

                Vector3 a = v0 - v1;
                Vector3 b = v2 - v1;
                Vector3 dir = Vector3.Cross(a, b);
                float x = Mathf.Abs(Vector3.Dot(dir, Vector3.right));
                float y = Mathf.Abs(Vector3.Dot(dir, Vector3.up));
                float z = Mathf.Abs(Vector3.Dot(dir, Vector3.forward));
                if (x > y && x > z)
                {
                    uvs[_i0] = new Vector2(v0.z, v0.y);
                    uvs[_i1] = new Vector2(v1.z, v1.y);
                    uvs[_i2] = new Vector2(v2.z, v2.y);
                }
                else if (y > x && y > z)
                {
                    uvs[_i0] = new Vector2(v0.x, v0.z);
                    uvs[_i1] = new Vector2(v1.x, v1.z);
                    uvs[_i2] = new Vector2(v2.x, v2.z);
                }
                else if (z > x && z > y)
                {
                    uvs[_i0] = new Vector2(v0.x, v0.y);
                    uvs[_i1] = new Vector2(v1.x, v1.y);
                    uvs[_i2] = new Vector2(v2.x, v2.y);
                }
                uvs[_i0] = new Vector2(range.xMin + (range.xMax - range.xMin) * uvs[_i0].x, range.yMin + (range.yMax - range.yMin) * uvs[_i0].y);
                uvs[_i1] = new Vector2(range.xMin + (range.xMax - range.xMin) * uvs[_i1].x, range.yMin + (range.yMax - range.yMin) * uvs[_i1].y);
                uvs[_i2] = new Vector2(range.xMin + (range.xMax - range.xMin) * uvs[_i2].x, range.yMin + (range.yMax - range.yMin) * uvs[_i2].y);
            }
        }

此代码片段是一个名为MapperCube的方法,它用于在一个立方体上进行UV映射。

首先,方法会检查uvs列表的大小是否小于vertices列表的大小,如果是,则会重新分配足够的空间。

然后,方法通过遍历triangles列表中的三角形索引,获取每个顶点的坐标。

通过将顶点坐标减去中心点并加上尺寸的一半,得到一个以中心点为原点的局部坐标系。

接下来,将局部坐标系中的坐标值除以立方体的尺寸值,得到归一化的UV坐标。

通过计算局部坐标系中两个向量的叉积,得到三角形的法向量。

接着,分别计算法向量在三个坐标轴上的投影长度,并比较三个投影长度的大小,确定UV映射的方式。

如果x投影长度最大,则将顶点的UV坐标设置为(v.z, v.y)。

如果y投影长度最大,则将顶点的UV坐标设置为(v.x, v.z)。

如果z投影长度最大,则将顶点的UV坐标设置为(v.x, v.y)。

最后,根据range矩形范围和计算出的UV坐标进行插值计算,将结果赋值给uvs列表中相应的索引位置。

该方法的作用是将三维立方体上的顶点映射到二维的UV坐标空间中,以便在渲染时进行贴图操作。

在三维计算机图形渲染中,UV映射是将三维模型的顶点映射到二维的纹理坐标空间中的过程。UV坐标确定了模型上每个顶点在纹理上的位置,使得纹理图案可以正确地贴在模型表面上。

以立方体为例,它有六个面,每个面都需要贴上纹理。为了实现这一目标,需要为每个顶点分配一个唯一的UV坐标。然后,根据模型的三角面片(triangles)定义,通过插值计算将UV坐标映射到三角面片上的其他点。

在上述代码中,为了获得每个顶点的UV坐标,首先将顶点的坐标值转换为局部坐标系,即以立方体中心为原点。然后,将局部坐标系中的坐标进行归一化处理,以确保UV坐标在(0,0)到(1,1)的范围内。

接下来,通过计算三角形的法向量及其在三个坐标轴上的投影长度,决定使用哪个坐标轴来映射UV。这样可以确保纹理在所有面片上的方向一致,避免出现纹理贴图的扭曲或扭转现象。

最后,使用给定的矩形范围和计算出的UV坐标进行插值计算,将结果赋值给uvs列表中相应的索引位置。这样,每个顶点都获得了正确的UV坐标,可以与纹理图案进行对应,从而实现正确的纹理贴图。

总的来说,UV映射是为了将模型上的顶点映射到纹理坐标空间中,以便在渲染时可以正确地将纹理贴图在模型表面上展示出来。该方法的目的是计算并为每个顶点分配正确的UV坐标。

所有代码

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace SplitMesh
{/// <summary>
/// Mesh网格数据类
/// </summary>
    public class MeshInfo
    {
        public List<Vector3> vertices;
        public List<int> triangles;
        public List<Vector2> uvs;
        public List<Vector3> normals;
        public List<Vector4> tangents;
        public Vector3 size, center;
        public MeshInfo()
        {
            vertices = new List<Vector3>();
            triangles = new List<int>();
            uvs = new List<Vector2>();
            normals = new List<Vector3>();
            tangents = new List<Vector4>();
            size = center = Vector3.zero;
        }
        public MeshInfo(Mesh mesh)
        {
            vertices = new List<Vector3>(mesh.vertices);
            triangles = new List<int>(mesh.triangles);
            uvs = new List<Vector2>(mesh.uv);
            normals = new List<Vector3>(mesh.normals);
            tangents = new List<Vector4>(mesh.tangents);
            center = mesh.bounds.center;
            size = mesh.bounds.size;
        }
        public void Add(Mesh mesh)
        {
            for (int i = 0; i < mesh.vertexCount; i++)
            {
                vertices.Add(mesh.vertices[i]);
                uvs.Add(mesh.uv[i]);
                normals.Add(mesh.normals[i]);
                tangents.Add(mesh.tangents[i]);
            }
            int length = triangles.Count;
            //我替换后也能正确运行的做法 int length = mesh.vertexCount;
            for (int i = 0; i < mesh.triangles.Length; i++)
            {
                triangles.Add(mesh.triangles[i] + length);
            }
        }
        public void Add(Vector3 vert, Vector2 uv, Vector3 normal, Vector4 tangent)
        {
            vertices.Add(vert);
            uvs.Add(uv);
            normals.Add(normal);
            tangents.Add(tangent);
        }
        public Mesh GetMesh()
        {
            Mesh mesh = new Mesh();
            mesh.vertices = vertices.ToArray();
            mesh.uv = uvs.ToArray();
            mesh.normals = normals.ToArray();
            mesh.tangents = tangents.ToArray();
            mesh.triangles = triangles.ToArray();
            return mesh;
        }
        //public void MapperSphere(Rect range){}
        public void MapperCube(Rect range)
        {
            if (uvs.Count < vertices.Count)
                uvs = new List<Vector2>(vertices.Count);
            int count = triangles.Count / 3;
            for (int i = 0; i < count; i++)
            {
                int _i0 = triangles[i * 3];
                int _i1 = triangles[i * 3 + 1];
                int _i2 = triangles[i * 3 + 2];

                Vector3 v0 = vertices[_i0] - center + size / 2f;
                Vector3 v1 = vertices[_i1] - center + size / 2f;
                Vector3 v2 = vertices[_i2] - center + size / 2f;
                v0 = new Vector3(v0.x / size.x, v0.y / size.y, v0.z / size.z);
                v1 = new Vector3(v1.x / size.x, v1.y / size.y, v1.z / size.z);
                v2 = new Vector3(v2.x / size.x, v2.y / size.y, v2.z / size.z);

                Vector3 a = v0 - v1;
                Vector3 b = v2 - v1;
                Vector3 dir = Vector3.Cross(a, b);
                float x = Mathf.Abs(Vector3.Dot(dir, Vector3.right));
                float y = Mathf.Abs(Vector3.Dot(dir, Vector3.up));
                float z = Mathf.Abs(Vector3.Dot(dir, Vector3.forward));
                if (x > y && x > z)
                {
                    uvs[_i0] = new Vector2(v0.z, v0.y);
                    uvs[_i1] = new Vector2(v1.z, v1.y);
                    uvs[_i2] = new Vector2(v2.z, v2.y);
                }
                else if (y > x && y > z)
                {
                    uvs[_i0] = new Vector2(v0.x, v0.z);
                    uvs[_i1] = new Vector2(v1.x, v1.z);
                    uvs[_i2] = new Vector2(v2.x, v2.z);
                }
                else if (z > x && z > y)
                {
                    uvs[_i0] = new Vector2(v0.x, v0.y);
                    uvs[_i1] = new Vector2(v1.x, v1.y);
                    uvs[_i2] = new Vector2(v2.x, v2.y);
                }
                uvs[_i0] = new Vector2(range.xMin + (range.xMax - range.xMin) * uvs[_i0].x, range.yMin + (range.yMax - range.yMin) * uvs[_i0].y);
                uvs[_i1] = new Vector2(range.xMin + (range.xMax - range.xMin) * uvs[_i1].x, range.yMin + (range.yMax - range.yMin) * uvs[_i1].y);
                uvs[_i2] = new Vector2(range.xMin + (range.xMax - range.xMin) * uvs[_i2].x, range.yMin + (range.yMax - range.yMin) * uvs[_i2].y);
            }
        }
        public void CombineVertices(float range)
        {
            range *= range;
            for (int i = 0; i < vertices.Count; i++)
            {
                for (int j = i + 1; j < vertices.Count; j++)
                {
                    bool dis = (vertices[i] - vertices[j]).sqrMagnitude < range;
                    bool uv = (uvs[i] - uvs[j]).sqrMagnitude < range;
                    bool dir = Vector3.Dot(normals[i], normals[j]) > 0.999f;
                    if (dis && uv && dir)
                    {
                        for (int k = 0; k < triangles.Count; k++)
                        {
                            if (triangles[k] == j)
                                triangles[k] = i;
                            if (triangles[k] > j)
                                triangles[k]--;
                        }
                        vertices.RemoveAt(j);
                        normals.RemoveAt(j);
                        tangents.RemoveAt(j);
                        uvs.RemoveAt(j);
                    }
                }
            }
        }
        public void Reverse()
        {
            int count = triangles.Count / 3;
            for (int i = 0; i < count; i++)
            {
                int t = triangles[i * 3 + 2];
                triangles[i * 3 + 2] = triangles[i * 3 + 1];
                triangles[i * 3 + 1] = t;
            }
            count = vertices.Count;
            for (int i = 0; i < count; i++)
            {
                normals[i] *= -1;
                Vector4 tan = tangents[i];
                tan.w = -1;
                tangents[i] = tan;
            }
        }

        public static Vector4 CalculateTangent(Vector3 normal)
        {
            Vector3 tan = Vector3.Cross(normal, Vector3.up);
            if (tan == Vector3.zero)
                tan = Vector3.Cross(normal, Vector3.forward);
            tan = Vector3.Cross(tan, normal);
            return new Vector4(tan.x, tan.y, tan.z, 1.0f);
        }
    }
}

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

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

相关文章

MyBatis学习简要

目录 什么是MyBatis? MyBatis实现的设想 MyBatis基于配置文件的开发步骤 mybatis的配置文件 Mapper代理开发 配置文件完成增删改查的三步 注解开发 一、条件查询 参数接收时&#xff0c;参数的设置&#xff1a; 动态条件查询&#xff1a; 二、添加功能 步骤&#xf…

SpringBootWeb案例 Part 5

4. 配置文件 员工管理的增删改查功能我们已开发完成&#xff0c;但在我们所开发的程序中还一些小问题&#xff0c;下面我们就来分析一下当前案例中存在的问题以及如何优化解决。 4.1 参数配置化 在我们之前编写的程序中进行文件上传时&#xff0c;需要调用AliOSSUtils工具类&…

基于RUM高效治理网站用户体验入门-价值篇

用户体验 用户体验基本包含访问网站的性能、可用性和正确性。通俗的讲&#xff0c;就是一把通过用户访问测量【设计者】意图的尺子。 本文目的 网站如何传递出设计者的意图&#xff0c;可能页面加载时间太长、或者页面在用户的浏览器中渲染时间太慢&#xff0c;或者第三方设备…

ssm+vue医院医患管理系统源码和论文

ssmvue医院医患管理系统源码和论文077 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm vue.js 摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已…

网络编程套接字(3): 简单的TCP网络程序

文章目录 网络编程套接字(3)4. 简单的TCP网络程序4.1 服务端创建(1) 创建套接字(2) 绑定端口(3) 监听(4) 获取新连接(5) 处理读取与写入 4.2 客户端创建(1)连接服务器 4.3 代码编写(1) v1__简单发送消息(2) v2_多进程版本(3) v3_多线程版本(4) v4_线程池版本 网络编程套接字(3)…

华为数通方向HCIP-DataCom H12-821题库(单选题:141-160)

第141题 Router-LSA 能够描述不同的链路类型&#xff0c;不属于Router LSA 链路类型的是以下哪一项? A、Link Type 可以用来描述到末梢网络的连接&#xff0c;即 SubNet B、Link Type 可以用来描述到中转网络的连接&#xff0c;即 TranNet C、Link Type 可以用来描述到另一…

mac软件安装后打开软件显示损坏

#mac传输安装包后安装后打开软件显示损坏处理方式 以postman为例&#xff0c;输入前面的代码&#xff0c;打开访达&#xff0c;把有问题的软件拉到命令行窗口&#xff0c;确认即可 sudo xattr -r -d com.apple.quarantine /Applications/Postman.app sudo xattr -r -d com.ap…

C语言的发展及特点

1. C语言的发展历程 C语言作为计算机编程领域的重要里程碑&#xff0c;其发展历程承载着无数开发者的智慧和创新。C语言诞生于20世纪70年代初&#xff0c;由计算机科学家Dennis Ritchie在贝尔实验室首次推出。当时&#xff0c;Ritchie的目标是为Unix操作系统开发一门能够更方便…

基于Java+SpringBoot+Vue前后端分离社区医院管理系统设计和实现

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

JavaScript—BOM

BOM是什么&#xff1f; Browser Object Model是浏览器对象模型 官方&#xff1a;浏览器对象模型提供了独立于内容的、可以与浏览器窗口进行互动的对象结构&#xff0c;BOM由多个对象构成&#xff0c;其中代表浏览器窗口的window对象是BOM的顶层对象&#xff0c;其他对象都是该…

HHDESK一键改密功能

HHDESK新增实用功能——使用SSH连接&#xff0c;对服务器/端口进行密码修改。 1 测试 首页点击资源管理——客户端&#xff0c;选择需要修改的连接&#xff1b; 可以先对服务器及端口进行测试&#xff0c;看是否畅通&#xff1b; 右键——测试——ping&#xff1b; 以及右…

【Prometheus】概述及部署

目录 Prometheus 概述 Prometheus 的生态组件 Prometheus 的工作模式 Prometheus 的工作流程 Prometheus 的局限性 部署 Prometheus Prometheust Server 端安装和相关配置 部署 Exporters 监控 MySQL 配置示例 监控 Nginx 配置示例 部署 Grafana 进行展示 部署 Pro…

Git仓库简介

1、工作区、暂存区、仓库 工作区&#xff1a;电脑里能看到的目录。 暂存区&#xff1a;工作区有一个隐藏目录.git&#xff0c;是Git的版本库&#xff0c;Git的版本库里存了很多东西&#xff0c;其中最重要的就是称为stage&#xff08;或者叫index&#xff09;的暂存区&#xf…

javacv基础02-调用本机摄像头并预览摄像头图像画面视频

引入架包&#xff1a; <dependency><groupId>org.openpnp</groupId><artifactId>opencv</artifactId><version>4.5.5-1</version></dependency><dependency><groupId>org.bytedeco</groupId><artifactId…

计算机网络MTU和MSS的区别

在计算机网络中&#xff0c;MTU代表最大传输单元&#xff08;Maximum Transmission Unit&#xff09;&#xff0c;而MSS代表最大分节大小&#xff08;Maximum Segment Size&#xff09;。 1.MTU&#xff08;最大传输单元&#xff09;&#xff1a; MTU是指在网络通信中&#x…

数据库——Redis 常见数据结构以及使用场景分析

文章目录 1. string2. list3. hash4. set5. sorted set 你可以自己本机安装 redis 或者通过 redis 官网提供的在线 redis 环境。 1. string 介绍 &#xff1a;string 数据结构是简单的 key-value 类型。虽然 Redis 是用 C 语言写的&#xff0c;但是 Redis 并没有使用 C 的字符串…

【Java架构-版本控制】-Git基础

本文摘要 Git作为版本控制工具&#xff0c;使用非常广泛&#xff0c;在此咱们由浅入深&#xff0c;分三篇文章&#xff08;Git基础、Git进阶、Gitlab搭那家&#xff09;来深入学习Git 文章目录 本文摘要1.Git仓库基本概念1.1 远程仓库(Remote)1.2 本地库(Repository) 2. Git仓库…

postman接口自动化测试框架实战!

什么是自动化测试 把人对软件的测试行为转化为由机器执行测试行为的一种实践。 例如GUI自动化测试&#xff0c;模拟人去操作软件界面&#xff0c;把人从简单重复的劳动中解放出来。 本质是用代码去测试另一段代码&#xff0c;属于一种软件开发工作&#xff0c;已经开发完成的用…

公网远程访问局域网SQL Server数据库

文章目录 1.前言2.本地安装和设置SQL Server2.1 SQL Server下载2.2 SQL Server本地连接测试2.3 Cpolar内网穿透的下载和安装2.3 Cpolar内网穿透的注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 数据库的重要性相信大家都有所了解&…

Jackpack - Hilt

一、概念 类中使用的某个对象不是在这个类中实例化的&#xff08;如Activity无法手动实例化使用&#xff09;&#xff0c;而是通过外部注入&#xff08;从外部传入对象后使用&#xff09;&#xff0c;这种实现方式就称为依赖注入 Dependency Injection&#xff08;简称DI&#…