Spine深入学习 —— 数据

news2024/12/24 2:32:18

atlas数据的处理

作用

图集,描述了spine使用的图片信息。

结构

page 页块

页块包含了页图像名称, 以及加载和渲染图像的相关信息。

page1.png
   size: 640, 480
   format: RGBA8888
   filter: Linear, Linear
   repeat: none
   pma: true
  • name: 首行为该页中的图像名称. 图片位置由atlas加载器来查找, 通常是再atlas文件的同目录下
  • size: 页中图像的宽度和高度. 在加载图像之前告知atlas加载器是非常有必要的, 例如, 可以提前为其分配缓冲区. 若省略则默认为0,0.
  • format: atlas加载器在内存中存储图像时应使用的格式. Atlas加载器可忽略该属性, 其可用值为: Alpha、Intensity、LuminanceAlpha、RGB565、RGBA4444、RGB888或RGBA8888. 若省略则默认为RGBA8888.
  • filter: Texture过滤器的缩略和放大方式. Atlas加载器可忽略该属性. 其可用值为: Nearest, Linear, MipMap, MipMapNearestNearest, MipMapLinearNearest, MipMapNearestLinear, 或MipMapLinearLinear. 若省略则默认为Nearest.
  • repeat: Texture包裹设置. Atlas加载器可忽略该属性. 其可用值为: x, y, xy, 或 none. 若省略则默认为none.
  • pma: 若值为true则表示图像使用了premultiplied alpha. 若省略则默认为false.
过滤方式 (OpenGL 纹理)

参考:https://learnopengl-cn.readthedocs.io/zh/latest/01%20Getting%20started/06%20Textures/

  • Nearest:对应GL的GL_NEAREST,邻近过滤。是OpenGL默认的纹理过滤方式。会选择中心点最接近纹理坐标的那个像素
    在这里插入图片描述

加号代表纹理坐标。左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色

  • Linear:对应GL的GL_LINEAR,线性过滤。会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色

    在这里插入图片描述

    一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大

具体区别如下图

在这里插入图片描述

GL_NEAREST产生了颗粒状的图案,我们能够清晰看到组成纹理的像素,而GL_LINEAR能够产生更平滑的图案,很难看出单个的纹理像素。GL_LINEAR可以产生更真实的输出。

以下是多级渐远纹理

问题:假设我们有一个包含着上千物体的大房间,每个物体上都有纹理。有些物体会很远,但其纹理会拥有与近处物体同样高的分辨率。由于远处的物体可能只产生很少的片段,OpenGL从高分辨率纹理中为这些片段获取正确的颜色值就很困难,因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色。在小物体上这会产生不真实的感觉,更不用说对它们使用高分辨率纹理浪费内存的问题了

解决:OpenGL使用一种叫做多级渐远纹理(Mipmap)的概念来解决这个问题,它简单来说就是一系列的纹理图像,后一个纹理图像是前一个的二分之一。多级渐远纹理背后的理念很简单:距观察者的距离超过一定的阈值,OpenGL会使用不同的多级渐远纹理,即最适合物体的距离的那个。由于距离远,解析度不高也不会被用户注意到。

在这里插入图片描述

  • MipMapNearestNearest:对应GL的GL_NEAREST_MIPMAP_NEAREST。使用最邻近的多级渐远纹理来匹配像素大小,并使用邻近插值进行纹理采样。
  • MipMapLinearNearest:对应GL的GL_LINEAR_MIPMAP_NEAREST。使用最邻近的多级渐远纹理级别,并使用线性插值进行采样
  • MipMapNearestLinear:对应GL的GL_NEAREST_MIPMAP_LINEAR。在两个最匹配像素大小的多级渐远纹理之间进行线性插值,使用邻近插值进行采样
  • MipMapLinearLinear:对应GL的GL_LINEAR_MIPMAP_LINEAR。在两个邻近的多级渐远纹理之间使用线性插值,并使用线性插值进行采样
premultiplied alpha

参考:https://segmentfault.com/a/1190000002990030

Alpha通道的工作原理: 最常见的像素表示格式是RGBA8888即 (r, g, b, a),每个通道8位,0-255。例如红色60%透明度就是 (255, 0, 0, 153),为了表示方便alpha通道一般记成正规化后的0-1的浮点数,也就是 (255, 0, 0, 0.6)

Premultiplied Alpha 则是把RGB通道乘以透明度也就是 (r * a, g * a, b * a, a),50%透明红色就变成了(153, 0, 0, 0.6)。

透明通道在渲染的时候通过 Alpha Blending 产生作用,如果一个透明度为 a s a_s as的颜色 C s C_s Cs渲染到颜色 C d C_d Cd上,混合后的颜色如下:

C o = α s C s + ( 1 − α s ) C d C_o = \alpha_{s} C_s + (1 - \alpha_{s})C_d Co=αsCs+(1αs)Cd

以60%透明的红色渲染到白色背景为例:

C o = ( 255 , 0 , 0 ) ⋅ 0.6 + ( 255 , 255 , 255 ) ⋅ ( 1 − 0.6 ) = ( 255 , 102 , 102 ) C_o = (255,0,0) \cdot 0.6 + (255,255,255) \cdot (1 - 0.6) = (255,102,102) Co=(255,0,0)0.6+(255,255,255)(10.6)=(255,102,102)

如果按照Premultiplied Alpha存储那么实际上的 α s C s \alpha_{s}C_s αsCs已经计算过了一次。

Premultiplied Alpha 之后,混合的时候可以少一次乘法,这可以提高一些效率,但这并不是最主要的原因。

最主要的原因是:

没有 Premultiplied Alpha 的纹理无法进行 Texture Filtering(除非使用最近邻插值)。
在这里插入图片描述

我们使用的PNG图片纹理,一般是不会 Premultiplied Alpha 的。游戏引擎在载入PNG纹理后回手动处理,然后再glTexImage2D传给GPU

比如 Cocos2D-x 中的 CCImage::premultipliedAlpha:

void Image::premultipliedAlpha() {
    unsigned int* fourBytes = (unsigned int*)_data;
    for (int i = 0; i < _width * _height; i++) {
        unsigned char* p = _data + i * 4;
        fourBytes[i] = CC_RGB_PREMULTIPLY_ALPHA(p[0], p[1], p[2], p[3]);
    }  
    _hasPremultipliedAlpha = true;
}

而GPU专用的纹理格式,比如 PVR、ETC 一般在生成纹理都是默认 Premultiplied Alpha 的,这些格式一般是GPU硬解码,引擎用CPU处理会很慢。

总之 glTexImage2D 传给 GPU 的纹理数据最好都是 Multiplied Alpha 的,要么在生成纹理时由纹理工具 Pre-multiplied,要么载入纹理后由游戏引擎或UI框架 Post-multiplied。

区域块 Region

区域块包含了页图像中的区域位置以及该区域的其他信息

bg-dialog
   index: -1
   rotate: false
   bounds: 519, 223, 17, 38
   offsets: 2, 2, 21, 42
   split: 10, 10, 29, 10
   pad: -1, -1, 28, 10

L1/L1_01
  rotate: true
  xy: 1855, 118
  size: 73, 78
  orig: 211, 216
  offset: 69, 69
  index: -1
  • name: 首行为区域名称, 用于在atlas中定位一个区域. 多个区域若索引(index)各不相同, 则它们可以同名.
  • index: 索引可以打包许多同名图像, 只要每个图像索引不同即可. 通常情况下, 索引是区域的帧号, 这些区域在逐帧动画中会依序绘制. 若省略则默认为-1.
  • bounds: 该图像在页图像中的像素位置x和y, 以及打包后图像尺寸, 即该图像在页图像中的像素尺寸. 若省略则默认为0,0,0,0.有些会以xy、size的方式记录
  • offsets: 在打包前, 要从图像的左侧和底部边缘去除的空白像素值, 以及原始图像尺寸(此图像在打包前的像素尺寸). 如果进行了去除操作, 则打包后的图像尺寸可能会小于原始图像. 若省略则左侧和底部像素偏移默认为0,0, 原始图像尺寸等于打包后的图像尺寸.
  • rotate: 若为true, 则表示该区域被逆时针旋转90度后存储在页图像中; 若为false则表示没有旋转. 属性值也可直接填入旋转角度(范围是0至360度). 若省略则默认为0.
  • split: 对图像进行九宫格分割, 属性值是从原图边缘算起的像素值. 分割所定义的3x3的网格, 可以在缩放图像时不用拉伸图像的所有部分. 若省略则默认为null.
  • pad: 九宫格分割后四周的填充厚度, 属性值是从原图边缘算起的像素值. 填充可以把置于九宫格中的内容以不同的方式嵌入到分片中. 若省略则默认为null.

数据结构

以下都以cocos2dx源码作为说明。

由上面可以知道,一个atlas文件由两个部分组成:Page和Region。

那么对于一个Atlas结构:

struct spAtlas {
	spAtlasPage* pages;
	spAtlasRegion* regions;

	void* rendererObject;
};

其中spAtlasPage如下:

struct spAtlasPage {
	const spAtlas* atlas;
	const char* name;
	spAtlasFormat format;
	spAtlasFilter minFilter, magFilter;
	spAtlasWrap uWrap, vWrap;

	void* rendererObject;
	int width, height;

	spAtlasPage* next;
};

其中spAtlasFormat对应的就是数据中的format,其结构为

typedef enum {
	SP_ATLAS_UNKNOWN_FORMAT,
	SP_ATLAS_ALPHA,
	SP_ATLAS_INTENSITY,
	SP_ATLAS_LUMINANCE_ALPHA,
	SP_ATLAS_RGB565,
	SP_ATLAS_RGBA4444,
	SP_ATLAS_RGB888,
	SP_ATLAS_RGBA8888
} spAtlasFormat;

spAtlasFilter对应filter

typedef enum {
	SP_ATLAS_UNKNOWN_FILTER,
	SP_ATLAS_NEAREST,
	SP_ATLAS_LINEAR,
	SP_ATLAS_MIPMAP,
	SP_ATLAS_MIPMAP_NEAREST_NEAREST,
	SP_ATLAS_MIPMAP_LINEAR_NEAREST,
	SP_ATLAS_MIPMAP_NEAREST_LINEAR,
	SP_ATLAS_MIPMAP_LINEAR_LINEAR
} spAtlasFilter;

spAtlasWrap对应split

typedef enum {
	SP_ATLAS_MIRROREDREPEAT,
	SP_ATLAS_CLAMPTOEDGE,
	SP_ATLAS_REPEAT
} spAtlasWrap;

最后的 spAtlasPage* next; 表示结构是用一个链表的方式存储。

在看看区域块Region的数据结构。

struct spAtlasRegion {
	const char* name;
	int x, y, width, height;
	float u, v, u2, v2;
	int offsetX, offsetY;
	int originalWidth, originalHeight;
	int index;
	int/*bool*/rotate;
	int/*bool*/flip;
	int* splits;
	int* pads;

	spAtlasPage* page;

	spAtlasRegion* next;
};

读取

读取函数为spAtlas_createFromFile

大概实现如下

spAtlas* spAtlas_createFromFile(const char* path, void* rendererObject) {
	int dirLength;
	char *dir;
	int length;
	const char* data;

	spAtlas* atlas = 0;

	/* Get directory from atlas path. */
	const char* lastForwardSlash = strrchr(path, '/');
	const char* lastBackwardSlash = strrchr(path, '\\');
	const char* lastSlash = lastForwardSlash > lastBackwardSlash ? lastForwardSlash : lastBackwardSlash;
	if (lastSlash == path) lastSlash++; /* Never drop starting slash. */
	dirLength = (int)(lastSlash ? lastSlash - path : 0);
	dir = MALLOC(char, dirLength + 1);
	memcpy(dir, path, dirLength);
	dir[dirLength] = '\0';
    
   //上面在处理路径,这里才是读取atlas文件。
	data = _spUtil_readFile(path, &length);
	//将文件流转换成spAtlas结构的对象。
	if (data) atlas = spAtlas_create(data, length, dir, rendererObject);

	FREE(data);
	FREE(dir);
	return atlas;
}

//读取文件,返回文件二进制数据和长度
char* _spUtil_readFile (const char* path, int* length) {
	Data data = FileUtils::getInstance()->getDataFromFile(FileUtils::getInstance()->fullPathForFilename(path));
	if (data.isNull()) return 0;

	// avoid buffer overflow (int is shorter than ssize_t in certain platforms)
#if COCOS2D_VERSION >= 0x00031200
	ssize_t tmpLen;
	char *ret = (char*)data.takeBuffer(&tmpLen);
	*length = static_cast<int>(tmpLen);
	return ret;
#else
    *length = static_cast<int>(data.getSize());
    char* bytes = MALLOC(char, *length);
    memcpy(bytes, data.getBytes(), *length);
    return bytes;
#endif
}

函数spAtlas_create 很长,大概作用就是对读出来的文件流进行解析,解析的格式按照
spAtlasPagespAtlasRegion结构体来。返回spAtlas对象。

对于spAtlas上的rendererObject传入都是0,对于spAtlasPage上rendererObject是保存page中的name属性对应的图片的纹理。具体创建是

void _spAtlasPage_createTexture (spAtlasPage* self, const char* path) {
	Texture2D* texture = Director::getInstance()->getTextureCache()->addImage(path);
	CCASSERT(texture != nullptr, "Invalid image");
	texture->retain();

	Texture2D::TexParams textureParams = {filter(self->minFilter), filter(self->magFilter), wrap(self->uWrap), wrap(self->vWrap)};
	texture->setTexParameters(textureParams);

	self->rendererObject = texture;
	self->width = texture->getPixelsWide();
	self->height = texture->getPixelsHigh();
}

目的是缓存纹理,提升速度。

使用

spAtlas被创建出来之后,会调用Cocos2dAttachmentLoader_create方法将spAtlas对象转换成spAttachmentLoader对象。

根据Spine官方文档中指出

创建SkeletonJson或SkeletonBinary实例需要指定一个AttachmentLoader,它包含返回新附件实例的方法。AttachmentLoader提供了一个加载时自定义附件的方式,如设置附件的图集区域以便稍后渲染。这个很常用,所以Spine运行时自带有一个功能完全相同的AtlasAttachmentLoader

Cocos2dAttachmentLoader* Cocos2dAttachmentLoader_create (spAtlas* atlas) {
	Cocos2dAttachmentLoader* self = NEW(Cocos2dAttachmentLoader);
	_spAttachmentLoader_init(SUPER(self), _Cocos2dAttachmentLoader_dispose, _Cocos2dAttachmentLoader_createAttachment,
		_Cocos2dAttachmentLoader_configureAttachment, _Cocos2dAttachmentLoader_disposeAttachment);
	self->atlasAttachmentLoader = spAtlasAttachmentLoader_create(atlas);
	return self;
}

spAtlasAttachmentLoader* spAtlasAttachmentLoader_create (spAtlas* atlas) {
	spAtlasAttachmentLoader* self = NEW(spAtlasAttachmentLoader);
	_spAttachmentLoader_init(SUPER(self), _spAttachmentLoader_deinit, _spAtlasAttachmentLoader_createAttachment, 0, 0);
	self->atlas = atlas;
	return self;
}

对于 spAtlasAttachmentLoader其中包含了spAtlas,不过添加了spAttachmentLoader的对象。

typedef struct spAtlasAttachmentLoader {
	spAttachmentLoader super;
	spAtlas* atlas;
} spAtlasAttachmentLoader;

typedef struct spAttachmentLoader {
	const char* error1;
	const char* error2;

	const void* const vtable;
#ifdef __cplusplus
	spAttachmentLoader () :
					error1(0),
					error2(0),
					vtable(0) {
	}
#endif
} spAttachmentLoader;

spAtlasAttachmentLoader_create创建了spAtlasAttachmentLoader对象,直接保存spAtlas对象,关键是 _spAttachmentLoader_init 函数,会去创建spAttachmentLoader 对象,该实现有值得学习的思路。

void _spAttachmentLoader_init (spAttachmentLoader* self,
	void (*dispose) (spAttachmentLoader* self),
	spAttachment* (*createAttachment) (spAttachmentLoader* self, spSkin* skin, spAttachmentType type, const char* name,
		const char* path),
	void (*configureAttachment) (spAttachmentLoader* self, spAttachment*),
	void (*disposeAttachment) (spAttachmentLoader* self, spAttachment*)
) {
	CONST_CAST(_spAttachmentLoaderVtable*, self->vtable) = NEW(_spAttachmentLoaderVtable);
	VTABLE(spAttachmentLoader, self)->dispose = dispose;
	VTABLE(spAttachmentLoader, self)->createAttachment = createAttachment;
	VTABLE(spAttachmentLoader, self)->configureAttachment = configureAttachment;
	VTABLE(spAttachmentLoader, self)->disposeAttachment = disposeAttachment;
}

他会去构建dispose、createAttachment、configureAttachment、disposeAttachment到结构体中,这样,可以直接通过结构体去调用该方法,每个结构体创建的时候可以传入不同的函数。

简单来说,spAtlasAttachmentLoader就是把spAtlas对象和其他需要用的方法以附件的方式加载到结构体中。

最后Cocos2dAttachmentLoader会配合JSON或者Binary文件来创建Spine。

cocos2dx-lua的改进

从源码上来看,对于Altals文件会IO从二进制,最后再解析从spAtlas对象。

创建也可以直接用spAtlas对象创建,这里减少对文件的IO。

对于lua的导出这里并没有实现的方法,估计是对spAtlas结构体的导出没有实现。

这里思路可以建立一个缓存机制,创建一次Spine,将其所有保存下来,下一次创建的时候,先检查缓存中是否有,如果有的话,那么就读缓存。

JSON和Binary

JSON

SkeletonJson,其好处是可读,缺点是文件较大,解析慢。

Binary

SkeletonBinary,不可读,但是加载速度快,文件小。

JSON数据

参考:https://zh.esotericsoftware.com/spine-json-format

Skeleton
"skeleton": {
   "hash": "5WtEfO08B0TzTg2mDqj4IHYpUZ4",
   "spine": "3.8.24",
   "x": -17.2,
   "y": -13.3,
   "width": 470.86,
   "height": 731.44,
   "images": "./images/",
   "audio": "./audio/"
},
  • hash: 所有skeleton数据的哈希值. 工具软件用它来检测Skeleton数据自上次加载后是否有变化.
  • version: 导出数据的Spine版本. 工具软件用它将Skeleton数据限制于某个特定的Spine版本.
  • x: skeleton附件AABB(axis-aligned bounding box)包围盒左下角点的X坐标, 与Spine中的setup pose相同.
  • y: skeleton附件AABB包围盒左下角点的Y坐标, 与Spine中的setup pose相同.
  • width: skeleton附件AABB包围盒宽度, 与Spine中的setup pose相同. 虽然skeleton的AABB盒取决于其pose, 但也可作为skeleton的大体尺寸.
  • height: skeleton附件AABB包围盒高度, 与Spine中的setup pose相同.
  • fps: Dopesheet(摄影表)的帧率, 单位为帧数/秒, 与Spine中相同. 若省略则默认为30. 非必要数据.
  • images: 图像文件路径, 与Spine中相同. 非必要数据.
  • audio: 音频文件路径, 与Spine中相同. 非必要数据.
骨骼(Bones)

导出文件的bones部分存储了setup pose状态的骨骼数据.

"bones": [
   { "name": "root" },
   { "name": "torso", "parent": "root", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 94.95 },
   ...
],

骨骼是有序的, 父骨骼总是排在子骨骼之前.

  • name: 骨骼名称, 该名称在skeleton上唯一.
  • length: 骨骼长度. 运行时通常不使用骨骼长度属性, 除非需要为骨骼绘制调试线. 若省略则默认为0.
  • transform: 该属性决定子骨骼以何种方式继承父骨骼的变换: normal, onlyTranslation, noRotationOrReflection, noScale或noScaleOrReflection. 若省略则默认为normal.
  • skin: 若为true, 则只有当活动皮肤包含该骨骼时该骨骼才算活动. 若省略则默认为false.
  • x: setup pose时骨骼相对于父对象位置的X值. 若省略则默认为0.
  • y: setup pose时骨骼相对于父对象位置的Y值. 若省略则默认为0.
  • rotation: setup pose时骨骼相对于父对象的旋转角度. 若省略则默认为0.
  • scaleX: setup pose时骨骼X方向的缩放比例. 若省略则默认为1.
  • scaleY: setup pose时骨骼Y方向的缩放比例. 若省略则默认为1.
  • shearX: setup pose时骨骼X方向的斜切角度. 若省略则默认为0.
  • shearY: setup pose时骨骼Y方向的斜切角度. 若省略则默认为0.
  • color: 骨骼的颜色, 与Spine中相同. 若省略则默认为0x989898FF RGBA. 非必要数据.
槽位(Slots)

槽位部分描述了绘制顺序和可分配附件的可用槽位.

"slots": [
   { "name": "left shoulder", "bone": "left shoulder", "attachment": "left-shoulder" },
   { "name": "left arm", "bone": "left arm", "attachment": "left-arm" },
   ...
],

如果没有槽位, "slots"部分可被省略. 槽位是按照setup pose的绘制顺序排序的. 索引较高的槽位中的图像被绘制在索引较低的槽位的图像之上.

  • name: 槽位名称. 该名称在skeleton上唯一.
  • bone: 该槽位所在骨骼的名称.
  • color: setup pose时槽位的颜色. 它是一个长度为8的字符串, 包含4个按RGBA顺序排列的两位十六进制数字. 若省略alpha, 则alpha默认为 “FF”. 若省略该属性则默认为 “FFFFFFF”.
  • dark: 设置setup pose时槽位用于双色tinting的dark color. 这是一个6个字符的字符串, 包含3个按RGB顺序排列的两位十六进制数字. 当不使用双色tinting时则省略.
  • attachment: setup pose时槽位中附件的名称. 若省略则默认setup pose没有附件.
  • blend: 在绘制槽位中可见附件时要使用的blend类别: normal, additive, multiply, 或screen.
约束(Constraints)
IK约束

IK约束用于设置骨骼旋转,可使骨骼末梢接触或指向目标骨骼。这有多种用途,但最常见的是通过移动手或脚来控制四肢。

"ik": [
   {
      "name": "left leg",
      "order": 2,
      "bones": [ "left thigh", "left shin" ],
      "target": "left ankle",
      "mix": 0.5,
      "bendPositive": false,
      "compress": true,
   },
   ...
],

如果没有IK约束, "ik"部分可被省略.

  • name: 约束名称. 该名称在skeleton上唯一.
  • order: 约束生效(applied)的顺序序数.
  • skin: 若为true, 则只有当活动皮肤包含该约束时该约束才生效. 若省略则默认为false.
  • bones: 一个包含1到2个骨骼名称的列表, 这些骨骼的旋转将被IK约束限制.
  • target: 目标(target)骨骼的名称.
  • mix: 一个介于0到1区间的值, 表示约束对骨骼的影响, 其中0表示只有FK, 1表示只有IK, 而中间值表示混合了FK和IK. 若省略则默认为1.
  • *softness: 对于双骨骼IK, 表示目标骨骼到旋转减缓前骨骼的最大活动范围的距离. 若省略则默认为0.
  • bendPositive: 若为true, 则骨骼的弯曲方向为正的旋转方向. 若省略则默认为false.
  • compress: 若为true且只有一个骨骼被约束, 则当目标太近时会缩放骨骼以保持连接. 若省略则默认为false.
  • stretch: 若为true且如果目标超出了范围, 将缩放父骨骼以保持连接. 若约束了多个骨骼且父骨骼的局部缩放比例非均匀(nonuniform), 则不应用拉伸(stretch). 若省略则默认为false.
  • uniform: 若为true且只约束了一个骨骼, 而且使用了压缩或拉伸, 则该骨骼将在X和Y方向上缩放. 若省略则默认为false.
Transform约束

变换约束可将骨骼的世界旋转、平移、缩放和/或剪切(其变换)复制到一个或多个其他骨骼

变换约束对高级装配有许多巧妙的用途。最简单的方法是移动一个骨骼,然后让其他骨骼也移动。它可用于模拟有不同父对象的骨骼,例如摘下帽子、装备武器或抛出物体。可以通过仅约束变换的子集(例如,仅限制缩放或剪切)来创建有趣的效果。可以将一个骨骼自动放置在其他两个骨骼之间距离的任意百分比等等。

"transform": [
   { "name": "weapon to hip", "order": 1, "bone": "weapon", "target": "hip" },
   ...
],

如果没有变换约束, "transform"部分可被省略.

  • name: 约束名称. 该名称在skeleton上唯一.
  • order: 约束生效(applied)的顺序序数.
  • skin: 若为true, 则只有当活动皮肤包含该约束时该约束才生效. 若省略则默认为false.
  • bones: 将被约束控制transform的骨骼.
  • target: 目标(target)骨骼的名称.
  • rotation: 相对于目标骨骼的旋转角度偏移量. 若省略则默认为0.
  • x: 相对于目标骨骼的X方向距离偏移量. 若省略则默认为0.
  • y: 相对于目标骨骼的Y方向距离偏移量. 若省略则默认为0.
  • scaleX: 相对于目标骨骼的X方向缩放偏移量. 若省略则默认为0.
  • scaleY: 相对于目标骨骼的Y方向缩放偏移量. 若省略则默认为0.
  • shearY: 相对于目标骨骼的Y方向斜切角度偏移量. 若省略则默认为0.
  • rotateMix: 一个介于0到1区间的值, 表示约束对骨骼的影响, 其中0表示无影响, 1表示只有约束, 而中间值表示正常pose和约束的混合. 若省略则默认为1.
  • translateMix: 参见 rotateMix.
  • scaleMix: 参见 rotateMix.
  • shearMix: 参见 rotateMix.
  • local: 如果需要影响目标的局部transform则设置为True, 反之则影响全局transform. 若省略则默认为false.
  • relative: 如果目标的transform为相对的则设置为True, 反之则其transform为绝对的. 若省略则默认为false.
Path约束

路径约束使用路径调整骨骼变换。骨骼可以沿路径平移,并将其旋转调整为指向路径。

路径约束可以替换平移关键帧,从而可以通过使用路径更轻松地定义移动。许多其他用途包括将多个骨骼约束到一条路径,然后通过操纵路径来控制骨骼,而非单独调整每个骨骼。例如,骨骼可以沿路径均匀分布,也可以放大,使它们看起来像是沿着路径生长。

"path": [
   {
      "name": "constraintName",
      "order": 0,
      "bones": [ "boneName1", "boneName2" ],
      "target": "slotName",
      "positionMode": "fixed",
      "spacingMode": "length",
      "rotateMode": "tangent",
      "rotation": "45",
      "position": "204",
      "spacing": "10",
      "rotateMix": "0",
      "translateMix": "1"
   },
   ...
],

如果没有路径约束, "path"部分可被省略.

  • name: 约束名称. 该名称在skeleton上唯一.
  • order: 约束生效(applied)的顺序序数.
  • skin: 若为true, 则只有当活动皮肤包含该约束时该约束才生效. 若省略则默认为false.
  • bones: 将被约束控制旋转或平移的骨骼.
  • target: 目标(target)骨骼的名称.
  • positionMode: 指定计算路径位置的方式: 定值(fixed)或百分比(percent). 若省略则默认为percent.
  • spacingMode: 指定骨骼间间距的计算方式: 长度(length), 定值(fixed)或百分比(percent). 若省略则默认为length.
  • rotateMode: 决定骨骼旋转的计算方式: 切线(tangent)、链式或链式缩放. 若省略则默认为tangent.
  • rotation: 相对于路径的旋转偏移量. 若省略则默认为0.
  • position: 路径位置. 若省略则默认为0.
  • spacing: 骨骼间间距. 若省略则默认为0.
  • rotateMix: 一个介于0到1区间的值, 表示约束对骨骼的影响, 其中0表示无影响, 1表示只有约束, 而中间值表示正常pose和约束的混合. 若省略则默认为1.
  • translateMix: 参见 rotateMix.
Skins(皮肤)
"skins": [
   {
      "name": "skinName",
      "attachments": {
         "slotName": {
            "attachmentName": { "x": -4.83, "y": 10.62, "width": 63, "height": 47 },
            ...
         },
         ...
      }
   },
   {
      "name": "skinName",
      "attachments": {
         "slotName": {
            "attachmentName": { "name": "actualAttachmentName", "x": 53.94, "y": -5.75, "rotation": -86.9, "width": 121, "height": 132 },
            ...
         },
         ...
      }
   },
   ...
],

如果没有皮肤或附件, "skins"部分可被省略.

每个皮肤本质上是一个映射(map), 键名是槽位和附件名称的复合键名, 其键值为一个附件. 虽然键中使用的附件名称对每个皮肤都相同, 但附件的实际附件名可能会有所不同.

当一个skeleton需要找到一个槽位中的一个附件时, 它首先会检查其皮肤. 如果没有找到, skeleton就会检查其默认皮肤. 默认皮肤包含没有被其他皮肤包含的附件, Spine可以混合使用皮肤和非皮肤附件. 默认皮肤总是带有"default"这个字样.

其他皮肤可能包含骨骼和/或约束:

"skins": [
   {
      "name": "skinName",
      "bones": [ "bone1", "bone2" ],
      "ik": [ "ik1", "ik2" ],
      "transform": [ "transform1", "transform2" ],
      "path": [ "path1", "path2" ],
      "attachments": {
         ...
      }
   },
   ...
}
附件(Attachments)

每个附件的属性根据附件类型的不同而不同.

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

事件(Events)

事件部分描述了在动画过程中可以触发的命名事件及其setup pose值.

"events" [
   "name": { "int": 1, "float": 2, "string": "three" },
   "name": { "int": 1, "float": 2, "string": "three", "audio": "hit.wav", "volume": 0.9, "balance": -0.25 },
   ...
],

在这里插入图片描述

动画(Animations)

一个动画包含一个时间轴列表. 每条时间轴均存储了一个关键帧列表, 这些时间轴描述了骨骼或槽位的值如何随时间变化.

"animations": {
   "name": {
      "bones": { ... },
      "slots": { ... },
      "ik": { ... },
      "deform": { ... },
      "events": { ... },
      "draworder": { ... },
   },
   ...
}
骨骼时间轴

动画的"bones"部分描述了控制骨骼的时间轴.

{
"bones": {
   "boneName": {
      "timelineType": [
         { "time": 0, "angle": -26.55 },
         { "time": 0.1333, "angle": -8.78 },
         ...
      ],
      ...
   },
   ...
},

每个关键帧的属性依时间轴的类型不同而变化.

在这里插入图片描述

槽位时间轴

动画的"slots"部分描述了操作槽位时间轴.

"slots": {
   "slotName": {
      "timelineType": [
         { "time": 0.2333, "name": "eyes closed" },
         { "time": 0.6333, "name": "eyes open" },
         ...
      ],
      ...
   },
   ...
}

每个关键帧的属性依时间轴的类型不同而变化.

在这里插入图片描述

IK约束时间轴

动画的"ik"部分描述了IK约束时间轴.

"ik": {
   "constraintName": [
      { "time": 0.5333, "mix": 0.616, "bendPositive": true },
      ...
   ],
   ...
},

在这里插入图片描述

Path约束时间轴

动画的"path"部分描述了路径约束时间轴.

"path": {
   "constraintName": 
      "position": [
         { "time": 1.81, "position": 0.7 },
         ...
      ],
      "spacing": [
         { "time": 2.92, "spacing": 0.05 },
         ...
      ],
      "mix": [
         { "time": 3.03, "rotateMix": 0.5, "translateMix": 0.75 },
         ...
      ],
   ...
},

在这里插入图片描述

Deform时间轴

动画的"deform"部分描述了用于操作每个网格附件的顶点(网格变形)的时间轴.

"deform": {
   "skinName": {
      "slotName": {
         "meshName": [
            {
               "time": 0,
               "curve": [ 0.25, 0, 0.75, 1 ]
            },
            {
               "time": 1.5,
               "offset": 12,
               "vertices": [ -0.75588, -3.68987, -1.01898, -2.97404, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
               -1.01898, -2.97404, -0.75588, -3.68987, 0, 0, -0.75588, -3.68987, -0.75588, -3.68987,
               -1.01898, -2.97404, -1.01898, -2.97404, -1.01898, -2.97404, -0.75588, -3.68987 ],
               "curve": [ 0.25, 0, 0.75, 1 ]
            },
            ...
         ],
         ...
      },
      ...
   },
   ...
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Event时间轴

动画的"events"部分描述了一个用于触发事件的时间轴.

"events": [
   { "time": 0.2, "name": "event1", "int": 1, "float": 2, "string": "three" },
   { "time": 0.6, "name": "event2", "int": 4, "float": 5, "string": "six" },
   ...
}

在这里插入图片描述

绘制顺序(Draw order)时间轴

动画的"draworder"部分描述了一个用于改变绘制顺序的时间轴, 它是一个skeleton上需绘制附件的槽位列表.

"draworder": [
   {
      "time": 0.2,
      "offsets": [
         { "slot": "slotName", "offset": 1 },
         { "slot": "slotName", "offset": -2 },
         ...
      ]
   },
   ...
}

在这里插入图片描述

二进制数据

参考:https://zh.esotericsoftware.com/spine-binary-format#%E6%A0%BC%E5%BC%8F

读取Json

typedef struct spSkeletonJson {
	float scale;
	spAttachmentLoader* attachmentLoader;
	const char* const error;
} spSkeletonJson;

spSkeletonJson本质上就是spAttachmentLoader的组合,加上一个缩放属性。

spSkeletonData* spSkeletonJson_readSkeletonDataFile (spSkeletonJson* self, const char* path) {
	int length;
	spSkeletonData* skeletonData;
	const char* json = _spUtil_readFile(path, &length);
	if (length == 0 || !json) {
		_spSkeletonJson_setError(self, 0, "Unable to read skeleton file: ", path);
		return 0;
	}
	skeletonData = spSkeletonJson_readSkeletonData(self, json);
	FREE(json);
	return skeletonData;
}

函数spSkeletonJson_readSkeletonData将json文件序列化spSkeletonData对象,即二进制对象。

typedef struct spSkeletonData {
	const char* version;
	const char* hash;
	float width, height;

	int bonesCount;
	spBoneData** bones;

	int slotsCount;
	spSlotData** slots;

	int skinsCount;
	spSkin** skins;
	spSkin* defaultSkin;

	int eventsCount;
	spEventData** events;

	int animationsCount;
	spAnimation** animations;

	int ikConstraintsCount;
	spIkConstraintData** ikConstraints;

	int transformConstraintsCount;
	spTransformConstraintData** transformConstraints;

	int pathConstraintsCount;
	spPathConstraintData** pathConstraints;
} spSkeletonData;

spBoneData骨骼的信息

typedef enum {
	SP_TRANSFORMMODE_NORMAL,
	SP_TRANSFORMMODE_ONLYTRANSLATION,
	SP_TRANSFORMMODE_NOROTATIONORREFLECTION,
	SP_TRANSFORMMODE_NOSCALE,
	SP_TRANSFORMMODE_NOSCALEORREFLECTION
} spTransformMode;

typedef struct spBoneData spBoneData;
struct spBoneData {
	const int index;
	const char* const name;
	spBoneData* const parent;
	float length;
	float x, y, rotation, scaleX, scaleY, shearX, shearY;
	spTransformMode transformMode;

#ifdef __cplusplus
	spBoneData() :
		index(0),
		name(0),
		parent(0),
		length(0),
		x(0), y(0),
		rotation(0),
		scaleX(0), scaleY(0),
		shearX(0), shearY(0),
		transformMode(SP_TRANSFORMMODE_NORMAL) {
	}
#endif
};

spSlotData插槽信息

typedef enum {
	SP_BLEND_MODE_NORMAL, SP_BLEND_MODE_ADDITIVE, SP_BLEND_MODE_MULTIPLY, SP_BLEND_MODE_SCREEN
} spBlendMode;

typedef struct spSlotData {
	const int index;
	const char* const name;
	const spBoneData* const boneData;
	const char* attachmentName;
	spColor color;
	spColor* darkColor;
	spBlendMode blendMode;

#ifdef __cplusplus
	spSlotData() :
		index(0),
		name(0),
		boneData(0),
		attachmentName(0),
		color(),
		darkColor(0),
		blendMode(SP_BLEND_MODE_NORMAL) {
	}
#endif
} spSlotData;

spSkin皮肤

typedef struct spSkin {
	const char* const name;

#ifdef __cplusplus
	spSkin() :
		name(0) {
	}
#endif
} spSkin;

spEventData事件数据

typedef struct spEventData {
	const char* const name;
	int intValue;
	float floatValue;
	const char* stringValue;

#ifdef __cplusplus
	spEventData() :
		name(0),
		intValue(0),
		floatValue(0),
		stringValue(0) {
	}
#endif
} spEventData;

spAnimation时间线数据

typedef struct spAnimation {
	const char* const name;
	float duration;

	int timelinesCount;
	spTimeline** timelines;

#ifdef __cplusplus
	spAnimation() :
		name(0),
		duration(0),
		timelinesCount(0),
		timelines(0) {
	}
#endif
} spAnimation;

spIkConstraintData IK约束数据

typedef struct spIkConstraintData {
	const char* const name;
	int order;
	int bonesCount;
	spBoneData** bones;

	spBoneData* target;
	int bendDirection;
	float mix;

#ifdef __cplusplus
	spIkConstraintData() :
		name(0),
		bonesCount(0),
		bones(0),
		target(0),
		bendDirection(0),
		mix(0) {
	}
#endif
} spIkConstraintData;

spTransformConstraintDataTransform约束数据

typedef struct spTransformConstraintData {
	const char* const name;
	int order;
	int bonesCount;
	spBoneData** const bones;
	spBoneData* target;
	float rotateMix, translateMix, scaleMix, shearMix;
	float offsetRotation, offsetX, offsetY, offsetScaleX, offsetScaleY, offsetShearY;
	int /*boolean*/ relative;
	int /*boolean*/ local;

#ifdef __cplusplus
	spTransformConstraintData() :
		name(0),
		bonesCount(0),
		bones(0),
		target(0),
		rotateMix(0),
		translateMix(0),
		scaleMix(0),
		shearMix(0),
		offsetRotation(0),
		offsetX(0),
		offsetY(0),
		offsetScaleX(0),
		offsetScaleY(0),
		offsetShearY(0),
		relative(0),
		local(0) {
	}
#endif
} spTransformConstraintData;

spPathConstraintData path路径约束

typedef struct spPathConstraintData {
	const char* const name;
	int order;
	int bonesCount;
	spBoneData** const bones;
	spSlotData* target;
	spPositionMode positionMode;
	spSpacingMode spacingMode;
	spRotateMode rotateMode;
	float offsetRotation;
	float position, spacing, rotateMix, translateMix;

#ifdef __cplusplus
	spPathConstraintData() :
		name(0),
		bonesCount(0),
		bones(0),
		target(0),
		positionMode(SP_POSITION_MODE_FIXED),
		spacingMode(SP_SPACING_MODE_LENGTH),
		rotateMode(SP_ROTATE_MODE_TANGENT),
		offsetRotation(0),
		position(0),
		spacing(0),
		rotateMix(0),
		translateMix(0) {
	}
#endif
} spPathConstraintData;

总的说来,就是读取json,按照数据结构解析成spSkeletonData对象。

读取二进制

void SkeletonRenderer::initWithBinaryFile (const std::string& skeletonDataFile, spAtlas* atlas, float scale) {
    _atlas = atlas;
    _attachmentLoader = SUPER(Cocos2dAttachmentLoader_create(_atlas));
    
    spSkeletonBinary* binary = spSkeletonBinary_createWithLoader(_attachmentLoader);
    binary->scale = scale;
    spSkeletonData* skeletonData = spSkeletonBinary_readSkeletonDataFile(binary, skeletonDataFile.c_str());
    CCASSERT(skeletonData, binary->error ? binary->error : "Error reading skeleton data file.");
    spSkeletonBinary_dispose(binary);
    
    setSkeletonData(skeletonData, true);
    
    initialize();
}

同样,解析成spSkeletonData数据的在spSkeletonBinary_readSkeletonDataFile

spSkeletonData* spSkeletonBinary_readSkeletonDataFile (spSkeletonBinary* self, const char* path) {
	int length;
	spSkeletonData* skeletonData;
	const char* binary = _spUtil_readFile(path, &length);
	if (length == 0 || !binary) {
		_spSkeletonBinary_setError(self, "Unable to read skeleton file: ", path);
		return 0;
	}
	skeletonData = spSkeletonBinary_readSkeletonData(self, (unsigned char*)binary, length);
	FREE(binary);
	return skeletonData;
}

spSkeletonBinary_readSkeletonData函数按照二进制的数据结构读取,然后解析成spSkeletonData,二进制的数据结构在

https://zh.esotericsoftware.com/spine-binary-format#%E4%BA%8C%E8%BF%9B%E5%88%B6%E5%AF%BC%E5%87%BA%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F

spSkeletonData数据结构和上面json的是一样的。

实例化数据对象

先看看官方给出的流程图

在这里插入图片描述

可以发现,解析完数据之后,就是instance Data,即实例化数据。

void SkeletonRenderer::setSkeletonData (spSkeletonData *skeletonData, bool ownsSkeletonData) {
	_skeleton = spSkeleton_create(skeletonData);
	_ownsSkeletonData = ownsSkeletonData;
}

拿骨骼举例,数据解析为spBoneData,那么实例化数据对象就是spBone

怎么理解呢?

Setup Pose Data,也就是解析Json或者Binary,对数据只是做了一次预处理,它并不会完全的去将所有数据处理成后面渲染的时候能用的数据。

两者有一定的区别

  1. 数据是无状态的,可在任意数量的骨架实例间共用
  2. 数据中的属性代表装配姿势,通常不会改动
  3. 实例对象中的相同属性表示播放动画时该实例的当前姿势。每个实例对象保有一个其数据参考,用于将实例对象重置回装配姿势。

可以理解成,数据是原始数据,一般不会涉及到对其进行转换修改等。实例数据对象就是将数据处理成后续渲染的时候需要用的数据,里面会有一些状态记录,坐标转换等方法。

这样做的一好处就是,备份原始数据,方便状态还原。

实例中有两个比较关键的方法,updateWorldTransformsetToSetUpPose

updateWorldTransform为更新世界变换,本质是触发骨骼位置的计算,由于骨骼位置可能发生旋转偏移,其对应的子骨骼也会受到影响,因此需要更新世界变换重新计算所有骨骼的最新坐标位置。

setToSetUpPose为更新实例到当前初始状态,一般才初始化时或重置人物状态时调用,会将人物形象骨骼装扮等切换为初始最初的状态。

Animation处理

实例对象生存之后,会调用Animation的处理

动画模块是被单独抽离出来的,目的是为了方便维护和更新实例的状态信息。

由动画state实例去触发skeleton实例的更新,接下来skeleton实例调用updateWorldTransform更新世界变化,之后重新上屏渲染。

void SkeletonAnimation::initialize () {
	super::initialize();

	_ownsAnimationStateData = true;
	_state = spAnimationState_create(spAnimationStateData_create(_skeleton->data));
	_state->rendererObject = this;
	_state->listener = animationCallback;
}

在cocos2dx中,类为SkeletonAnimation,继承于SkeletonRenderer

该函数会生存两个对象:

  • spAnimationStateData:存储AnimationState动画更改时要应用的混合(交叉淡入淡出)持续时间。
  • spAnimationState : 随着时间调用动画,动画入队等待播放,允许多个动画叠加。分多个track存储动画、区分不同动画的timeline,针对event事件的处理逻辑等。

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

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

相关文章

电流模式的PWM控制电路芯片D3846,封装形式采用DIP16/SOIC16,内置差动电流检测放大器, 共模输入范围宽

D3846是一块电流模式的PWM控制电路。 主要特点&#xff1a; ● 自动前馈补偿 ● 可编程控制的逐个脉冲限流功能 ● 推挽输出结构^ 下自动对称校正 ● 负载响应特性好 ● 可并联运行&#xff0c;适用于模块系统 ● 内置差动电流检测放大器&#xff0c; 共模输入范围宽 ● 双脉冲…

反思一次效能提升

前天与一个大佬交流。想起自己在6年多前在团队里做的一次小小的效能提升。 改进前 在同一个产品团队&#xff0c;同时有前端工程师和后端工程师。他们经常需要共同协作完成features。 前端是一个传统的多页应用。前端渲染是由后端的velocity模板引擎实现的。 打包后&#xff0c…

【浏览器】录音open失败:浏览器禁止不安全页面录音,可开启https解决..

在浏览器地址栏中输入&#xff1a;chrome://flags/#unsafely-treat-insecure-origin-as-secure 启动选项&#xff0c;并且添加你本地的开发地址

多actor实体组合并统一应用变换_vtkAssembly

开发环境&#xff1a; Windows 11 家庭中文版Microsoft Visual Studio Community 2019VTK-9.3.0.rc0vtk-example参考代码 demo解决问题&#xff1a;创建了一个球体和立方体的三维可视化&#xff0c;将它们组合成一个装配体&#xff0c;应用变换&#xff0c;调整不透明度&#…

C#,《小白学程序》第三课:类class,类的数组及类数组的排序

类class把数值与功能巧妙的进行了结合&#xff0c;是编程技术的主要进步。 下面的程序你可以确立 分数 与 姓名 之间关系&#xff0c;并排序。 1 文本格式 /// <summary> /// 同学信息类 /// </summary> public class Classmate { /// <summary> /…

使用 JavaScript 进行 API 测试的综合教程

说明 API 测试是软件测试的一种形式&#xff0c;涉及直接测试 API 并作为集成测试的一部分&#xff0c;以确定它们是否满足功能、可靠性、性能和安全性的预期。 先决条件&#xff1a; JavaScript 基础知识。Node.js 安装在您的计算机上。如果没有&#xff0c;请在此处下载。npm…

设计模式—里氏替换原则

1.概念 里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说&#xff0c;任何基类可以出现的地方&#xff0c;子类一定可以出现。 LSP是继承复用的基石&#xff0c;只有当衍生类可以替换掉基类&#xff0c;软件单位的功能不受到影…

K8S客户端一 Rancher的安装

一 安装方式一 通过官网方式安装&#xff1a;官网 sudo docker run --privileged -d --restartunless-stopped -p 80:80 -p 443:443 rancher/rancher:stable访问服务器地址即可&#xff1a;http://192.168.52.128 修改语言 第一次安装会生成密码&#xff0c;查看密码步骤如下…

CANdelaStudio 使用教程3 新建Service

文章目录 简述Service 的相关配置项1、Protocol Services2、Diagnostic Class Templates3、Supported Diagnostic Classes 新建 Service1、新建 Service2、新建类并添加服务3、 选择支持的服务4、Diagnostic Class Templates&#xff1a;Identification 编辑 Service1、新增服务…

Git 与 Maven:企业级版本管理与版本控制规范设计

一、背景 当今&#xff0c;许多开发人员熟悉 GitFlow 工作流程&#xff0c;但往往忽略了 GitFlow 如何与 Maven 版本控制结合&#xff0c;尤其是在管理 snapshot 和 release 版本时的最佳实践。本文旨在整合 GitFlow 工作流程与 Maven 版本管理&#xff0c;提出一个统一的企业…

深入理解数据结构:链表

文章目录 &#x1f330;导语&#x1f330;链表的定义及基本结构&#x1f330;单链表&#x1f955;单链表特点 &#x1f330;双向链表&#x1f955;双链表特点 &#x1f330;循环链表&#x1f955;循环链表特点 &#x1f330;链表的操作&#x1f346;链表的插入&#x1fad8;链头…

GoLang Filepath.Walk遍历优化

原生标准库在文件量过大时效率和内存均表现不好 1400万文件遍历Filepath.Walk 1400万文件重写直接调用windows api并处理细节 结论 1400万文件遍历时对比 对比条目filepath.walkwindows api并触发黑科技运行时间710秒22秒内存占用480M38M 关键代码 //超级快的文件遍历 fun…

ONNX实践系列-将dbnet.onnx的hardsigmoid op用hardsigmoid.onnx整个去替换掉

一、目标 这个dbnet.onnx是paddleocr转出来的,自带的有paddle的那个hardsigmoid算子 ,这个不好转到trt等框架,因此我们想把这个hardsigmoid 算子op替换成我们常规的pytorch框架转出来的hardsigmoid onnx那种。 二、做法 给出代码如下: import onnx from onnx import help…

2023年汉字小达人市级比赛题目类型和答题策略(在线模拟题更新)

今天是2023年11月24日&#xff0c;距离2023年第十届上海市小学生汉字小达人市级比赛&#xff08;市级活动&#xff09;正式举办还有6天时间。 根据日常交流&#xff0c;六分成长发现还有一些家长和小朋友对汉字小达人的市级比赛的形式、题型不太了解&#xff0c;为此&#xff0…

Day40力扣打卡

打卡记录 包子凑数&#xff08;裴蜀定理 DP&#xff09; 根据裴蜀定理&#xff0c;存在 c gcd(a, b) 使不定方程ax by c满足条件&#xff0c;如果gcd(a, b) 1即a与b互素的情况下&#xff0c;就会 ax by 1&#xff0c;由于为1可以构造后面的无穷数字&#xff0c;故得到结…

DBS note5:Relational Algebra(关系代数)

目录 一、关系代数简介 二、Projection () 三、Selection () 四、Union () 五、Set Difference (-) 六、Intersection () 七、Cross Product () 八、Joins () 九、Rename () 十、Group By / Aggregation () 一、关系代数简介 关系代数中的所有运算符都接受一个关系并…

硬件连通性测试主要作用是什么?

硬件连通性测试是现代工程和制造中不可或缺的一环&#xff0c;它通过验证硬件系统内各个组件之间的通信和协作&#xff0c;确保系统在投入使用前能够正常运行。这一关键测试方法不仅有助于提高系统的可靠性和稳定性&#xff0c;还能在生产过程中节省成本&#xff0c;加快生产效…

Exchange意外登录日志

最近在审计Exchange邮件系统的时候&#xff0c;发现大量用户半夜登录的日志。而且都是成功的&#xff0c;几乎没有失败的情况。其中Logon Type 8表示用户从网络登录。 Logon type 8: NetworkCleartext. A user logged on to this computer from the network. The user’s pas…

机器学习算法——主成分分析(PCA)

目录 1. 主体思想2. 算法流程3. 代码实践 1. 主体思想 主成分分析&#xff08;Principal Component Analysis&#xff09;常用于实现数据降维&#xff0c;它通过线性变换将高维数据映射到低维空间&#xff0c;使得映射后的数据具有最大的方差。主成分可以理解成数据集中的特征…

rancher2.6 docker版本部署

1. 拉取镜像 docker pull rancher/rancher:v2.6.5 注&#xff1a; 上面命令中rancher的版本v2.6.5&#xff0c;仅仅是我因为我们环境中使用的k8s都是 1.20.1 到1.23.6 之间的版本。rancher支持的k8s版本&#xff0c;在github上查看&#xff1a;Release Release v2.6.5 ranche…