Spine3.5骨骼文件数据结构解析
Spine运行时架构
引用自Spine官网。

基础数据类型
Boolean布尔值
boolean型占用1字节(byte), 值为1代表true而0代表false.
简单的8位数值,占用1字节。
Short短整型
int readShort () { return (readByte() << 8) | readByte(); }
Short类型为16位数值,占用2字节。
0b1111_0000_1100_0011 = [0b1111_0000, 0b1100_0011]
其中高8位储存为第一个字节,低8位储存为第二个字节。
Int值
int readInt () { return (readByte() << 24) | (readByte() << 16) | (readByte() << 8) | readByte(); }
int型为32位数值, 占用4字节。
0b11111111_00000000_11111111_00000000 = [0b11111111, 0b00000000, 0b11111111, 0b00000000]
从高到低按每8位为一段,第一段为第一个字节,第二段为第二个字节,以此类推。
Varint可变长整型
int readVarint (int/*bool*/optimizePositive) { unsigned char b = readByte(); int value = b & 0x7F; if (b & 0x80) { b = readByte(); value |= (b & 0x7F) << 7; if (b & 0x80) { b = readByte(); value |= (b & 0x7F) << 14; if (b & 0x80) { b = readByte(); value |= (b & 0x7F) << 21; if (b & 0x80) value |= (readByte() & 0x7F) << 28; } } } if (!optimizePositive) value = (((unsigned int)value >> 1) ^ -(value & 1)); return value; }
可变长整数值,视数值大小占用1-5个字节。
一个变长整型(varint)就是一个int, 但是它的存储占用为1到5个字节(取决于数值的大小). varint有两种,
varint+用于以更小的空间占用来存储的小正值,varint-用于小负值(和正值).对于varint中的每个字节, 若有额外字节就使用MSB. 若需要存储小负值则使用移位.
Varint+类型
varint+可变长整数一般用于存储正值(也可存储负值,但固定占用5字节),其将一个整数拆为了4+7+7+7+7(从高到低)最多五个字节,每个字节的高位决定了其左边是否还有剩余的字节,即:
- 有一个整数
0b01100110_01100110_01100110_01100110(0b01100110011001100110011001100110) - 将其拆分为5块(从高到低)
- part1:0b
01100000000000000000000000000000 - part2:0b
00000110011000000000000000000000 - part3:0b
00000000000001100100000000000000 - part4:0b
00000000000000000010011000000000 - part5:0b
00000000000000000000000001100110
- part1:0b
- 将每一块转换为
bytepart1 >>> 28得0b00000110(part1没有更左边的字节故高位置0)part2 >>> 21 | 0b10000000得0b10110011(part1有值故高位置1)part3 >>> 14 | 0b10000000得0b10011001(part2有值故高位置1)part4 >>> 07 | 0b10000000得0b11001100(part3有值故高位置1)part5 >>> 00 | 0b10000000得0b11100110(part4有值故高位置1)
- 写入时从低到高写入,即:
- 写入part5:
0b11100110 - 写入part4:
0b11001100 - 写入part3:
0b10011001 - 写入part2:
0b10110011 - 写入part1:
0b00000110
- 写入part5:
/**
* 将int数值转换为可变长整型
*
* @param num 待转换数值
* @return 转换后数值拆分数组,长度在1-5之间
*/
public static int[] toPositiveVarint(int num) {
int[] parts = {num >>> 28, num << 4 >>> 25, num << 11 >>> 25, num << 18 >>> 25, num << 25 >>> 25};
int startIndex = 0;
for (int i = 0; i < parts.length; i++) {
if (parts[i] != 0) {
startIndex = i;
break;
}
}
int[] validParts = Arrays.copyOfRange(parts, startIndex, parts.length);
for (int i = 1; i < validParts.length; i++) {
validParts[i] = validParts[i] | 0b10000000;
}
return validParts;
}
Varint-类型
varint-类型用于存储负值,也可用于存储正值。要存储负值,需先进行负值转化后才能拆为5部分进行写入。
/**
* 将int数值转换为Spine的varint-数值
*
* @param num 待转换数值
* @return 转换后数值
*/
public static int toNegativeVarint(int num) {
int symbol = num >>> 31;
return ((num ^ -symbol) << 1) | symbol;
}
Float浮点数
float型为32位数值, 占用4字节. 根据语言和架构的不同, 这些字节可以合并为一个int, 然后类型转换为一个float值.
32位浮点数占4字节,与int类型相同,故Spine将float储存为了一个int值。
public static byte[] intToBytes(int num) {
return new byte[]{
(byte) ((num >>> 24) & 0x000000ff),
(byte) ((num >>> 16) & 0x000000ff),
(byte) ((num >>> 8) & 0x000000ff),
(byte) (num & 0x000000ff),};
}
public static int floatToInt(float num) {
return Float.floatToRawIntBits(num);
}
public static byte[] floatToBytes(float num) {
return intToBytes(floatToInt(num));
}
String字符串
string型是以0结尾的变长UTF-8字符串. 若长度为0, 则字符串为null(在大多数情况下,也可当作空白字符串). 若长度为1, 则字符串为空. 其他情况下, 内存占用为length - 1字节. 一个UTF-8符可能不止一个字节, 所以string可能会占用更多的字节.
Spine中字符串以【字符串Byte长度,字符串Byte数组,'\0'】组成。当Byte长度为0时代表null,为1时代表空字符串“”,其他情况则为字符串字节数+1;Byte数组为字符串UTF8格式编码的字节数组。
public static byte[] strToBytes(String str) {
if (str == null) {
return toPositiveVarint(0);
}
if (str.isEmpty()) {
return toPositiveVarint(1);
}
byte[] charBytes = str.getBytes(StandardCharsets.UTF_8);
byte[] lengthBytes = toPositiveVarint(charBytes.length + 1);
return ArrayUtil.addAll(lengthBytes, charBytes);
}
/**
* Reads the length and string of UTF8 characters, or null.
*
* @return May be null.
*/
public String readString() throws IOException {
int byteCount = readInt(true);
switch (byteCount) {
case 0:
return null;
case 1:
return "";
default:
}
byteCount--;
if (chars.length < byteCount) chars = new char[byteCount];
char[] chars = this.chars;
int charCount = 0;
for (int i = 0; i < byteCount; ) {
int b = read();
switch (b >> 4) {
case -1:
throw new EOFException();
case 12:
case 13:
// 读取2字节的字符串
chars[charCount++] = (char) ((b & 0b0001_1111) << 6 | read() & 0b0011_1111);
i += 2;
break;
case 14:
// 读取3字节的字符串(例如中文)
chars[charCount++] = (char) ((b & 0b0000_1111) << 12
| (read() & 0b0011_1111) << 6
| read() & 0b0011_1111);
i += 3;
break;
default:
chars[charCount++] = (char) b;
i++;
}
}
return new String(chars, 0, charCount);
}
字符串支持中文。
Color色彩
Spine的Color数据类型在二进制文件中表现为一个4字节的int类型,在内存中表现为一个长度为4的float数组,从0-3依次为Red、Green、Blue、Alpha。
public static float[] intToColor(int color) {
return new float[] {
((color & 0xff000000) >>> 24) / 255f,
((color & 0x00ff0000) >>> 16) / 255f,
((color & 0x0000ff00) >>> 8) / 255f,
((color & 0x000000ff)) / 255f,};
}
public static int colorToInt(float[] rgba) {
return (int) (rgba[0] * 255f) << 24
| (int) (rgba[1] * 255f) << 16
| (int) (rgba[2] * 255f) << 8
| (int) (rgba[3] * 255f);
}
在存储为int数值时,RGBA每个通道占8bit,从高到低乘以255F后依次拼接到int类型上,相应的从int类型读取通道时也需要除以255F。
复杂数据对象
Spine的复杂数据对象由上述的基础数据类型组合而来,包括以下类别:
-
文件头
head -
骨骼
bone -
槽位
slot -
逆运动学约束
ik constraint-
Inverse Kinematics Constraint是逆运动学约束。
逆运动学约束用于确定机器人模型的关节配置,以实现所需的最终效果位置。基于关节之间的转换,在rigidBodyTree机器人模型中指定了机器人运动学约束。还可以指定外部约束,例如摄像机臂的瞄准约束或特定刚体链接上的笛卡尔边界框。使用机械手约束对象和generalizedInverseKinematics对象。inverseKinematics
inverseKinematics系统对象创建一个逆运动学(IK)求解器,对于指定的刚体树模型为期望的末端执行器姿势计算各个关节角度。
-
-
变换约束
transform constraint -
路径约束
path constraint -
皮肤
skin -
事件
event -
动画
animation -
时间线
timeline -
关键帧
key frame -
曲线
curve

浙公网安备 33010602011771号