1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
//! 网格的序列化中间结构(store 层):绑定姿势网格与本地网格两种存储形式,可转换为运行时 [`crate::data::runtime::mesh::MeshData`]。
use serde::{Deserialize, Serialize};
use crate::data::runtime::mesh::MeshMetadata;
/// 存储用的蒙皮网格数据
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct MeshBindDataStore {
#[serde(flatten)]
pub meta: MeshMetadata,
/// 顶点在绑定姿势下的位置(推荐:世界空间或根骨骼空间)
/// 格式: [x0, y0, x1, y1, ...]
pub vertices_bind: Vec<f32>,
/// 纹理名称
/// 一般对应于实际纹理中的一个子区域,会有另外的一个文件进行存储
pub texture_name: String,
/// UV 坐标
/// 以局部纹理坐标为准,运行时转换
/// 如果网格不需要uv,例如裁剪/交互检测等,可省略此字段,默认为全0值
/// 格式: [u0, v0, u1, v1, ...]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uvs: Option<Vec<f32>>,
/// 顶点数量
pub vertex_count: u32,
/// 三角形索引(uint16 足够用于 2D)
pub triangles: Vec<u16>,
// === 骨骼绑定信息 ===
// 最好按照主骨骼在前排列,以应用省略influence_bone_indices的规则
/// 此 mesh 用到的所有骨骼名称(去重列表)
/// 例: ["arm", "hand", "leg", ...]
pub bones: Vec<String>,
/// 每个骨骼对应的 inverse bind matrix(3x3,定义如下)
/// 长度 = bones.len()
/// 用于计算: bind_local = inv_bind_matrix\[i\] * homogeneous(P_bind)
///
/// 存储为 6 个 f32: [a, b, c, d, x, y],对应矩阵:
/// a, b, x,
/// c, d, y,
/// 0, 0, 1
/// 其中 (a,b,c,d) 是 2x2 线性变换矩阵,(x,y) 是平移分量
pub inverse_bind_matrices: Vec<[f32; 6]>,
// === 顶点影响数据(扁平化、紧凑)===
// 存储时节点按照受到影响骨骼数量从大到小排列,省略末尾的单骨骼顶点影响数据以节省空间
/// 每个顶点的影响骨骼数量
/// 长度 = vertex_count,如果不存在或长度不合适自动追加填充为全1值(只受到1骨骼影响)
/// 存储顺序与运行时一致。仅增加省略规则
#[serde(default, skip_serializing_if = "Option::is_none")]
pub influence_counts: Option<Vec<u8>>,
/// 影响骨骼索引(指向 `bones` 的索引)
/// 长度 = Σ(influence_counts),如果不存在或长度不合适自动追加填充为Σ(influence_counts)个全0值(第一个骨骼)
/// 存储顺序与运行时一致。仅增加省略规则
#[serde(default, skip_serializing_if = "Option::is_none")]
pub influence_bone_indices: Option<Vec<u8>>,
/// 对应权重
/// 长度 = Σ(influence_counts),如果不存在或长度不合适自动追加填充为Σ(influence_counts)个全1值(完全受到这个骨骼影响)
/// 权重和应为 1.0(工具链应保证)
/// 存储顺序与运行时一致。仅增加省略规则
#[serde(default, skip_serializing_if = "Option::is_none")]
pub influence_weights: Option<Vec<f32>>,
}
/// 存储用的蒙皮网格数据
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
pub struct MeshLocalDataStore {
#[serde(flatten)]
pub meta: MeshMetadata,
/// 纹理名称
/// 一般对应于实际纹理中的一个子区域,会有另外的一个文件进行存储
pub texture_name: String,
/// UV 坐标
/// 图集坐标(页内 0..1):spine 导入后经 [crate::data::store::spine::apply_atlas] 从 region 局部坐标转换
/// 如果网格不需要uv,例如裁剪/交互检测等,可省略此字段,默认为全0值
/// 格式: [u0, v0, u1, v1, ...]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uvs: Option<Vec<f32>>,
/// 顶点数量
pub vertex_count: u32,
/// 三角形索引(uint16 足够用于 2D)
pub triangles: Vec<u16>,
// === 骨骼绑定信息 ===
// 最好按照主骨骼在前排列,以应用省略influence_bone_indices的规则
/// 此 mesh 用到的所有骨骼名称(去重列表)
/// 例: ["arm", "hand", "leg", ...]
/// 如果为空,则表示所有顶点均只受单骨骼影响,使用的骨骼为Slot的主骨骼
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bones: Option<Vec<String>>,
// === 顶点影响数据(扁平化、紧凑)===
// 存储时节点按照受到影响骨骼数量从大到小排列,省略末尾的单骨骼顶点影响数据以节省空间
/// 每个顶点的影响骨骼数量
/// 长度 = vertex_count,如果不存在或长度不合适自动追加填充为全1值(只受到1骨骼影响)
/// 存储顺序与运行时一致。仅增加省略规则
#[serde(default, skip_serializing_if = "Option::is_none")]
pub influence_counts: Option<Vec<u8>>,
/// 顶点在各个骨骼空间的位置
/// 存储顺序:按顶点索引 i 从 0 到 N-1 遍历,
/// 对每个顶点 i,若其受 `influence_counts[i] = n` 个骨骼影响,
/// 则紧接着写入 n 个 (x, y) 坐标,共 2*n 个 f32。
/// 这些坐标的骨骼顺序必须与 `influence_bone_indices` 中对应段完全一致。
///
/// 示例:
/// - 顶点0:受骨骼\[1, 2\]影响 → 写入 [x0_in_bone1, y0_in_bone1, x0_in_bone2, y0_in_bone2]
/// - 顶点1:受骨骼\[0\]影响 → 写入 [x1_in_bone0, y1_in_bone0]
///
/// 存储数据为:
///
/// [x0_in_bone1, y0_in_bone1, x0_in_bone2, y0_in_bone2, x1_in_bone0, y1_in_bone0, ...]
///
/// 总长度 = 2 * Σ(influence_counts)
pub vertices_locals: Vec<f32>,
/// 影响骨骼索引(指向 `bones` 的索引)
/// 长度 = Σ(influence_counts),如果不存在或长度不合适自动追加填充为Σ(influence_counts)个全0值(第一个骨骼)
/// 存储顺序与运行时一致。仅增加省略规则
#[serde(default, skip_serializing_if = "Option::is_none")]
pub influence_bone_indices: Option<Vec<u8>>,
/// 对应权重
/// 长度 = Σ(influence_counts),如果不存在或长度不合适自动追加填充为Σ(influence_counts)个全1值(完全受到这个骨骼影响)
/// 权重和应为 1.0(工具链应保证)
/// 存储顺序与运行时一致。仅增加省略规则
#[serde(default, skip_serializing_if = "Option::is_none")]
pub influence_weights: Option<Vec<f32>>,
/// 裁剪附件标记:Some(end_slot 名称) 表示该网格是裁剪多边形(无 UV/三角形),
/// 裁剪范围覆盖本 slot 之后、end_slot 之前(不含 end_slot)的所有网格
#[serde(default, skip_serializing_if = "Option::is_none")]
pub clip_end_slot: Option<String>,
}
/// 网格的两种存储形式(反序列化时自动识别,untagged):
/// - [`MeshStore::BindAndIBM`]:绑定姿势 + 逆绑定矩阵(`MeshBindDataStore`)
/// - [`MeshStore::BoneLocal`]:骨骼局部坐标 + 权重(`MeshLocalDataStore`)
#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(untagged)]
pub enum MeshStore {
BindAndIBM(MeshBindDataStore),
BoneLocal(MeshLocalDataStore),
}
mod to_rt {
use glam::{Affine2, Vec2, mat2, vec2};
/// 标准化后的 influence 数据(counts, bone indices, weights)
type Influences = (Vec<u8>, Vec<u8>, Vec<f32>);
use crate::{
data::{
runtime::{mesh::MeshData, skeleton::Skeleton, slot::SlotSet},
store::mesh::{MeshBindDataStore, MeshLocalDataStore, MeshStore},
},
error::Error,
};
impl MeshBindDataStore {
pub fn to_rt(self, skeleton_data: &Skeleton) -> Result<MeshData, Error> {
let vertex_count = self.vertex_count as usize;
let uvs = self.uvs.unwrap_or_else(|| vec![0.0; vertex_count * 2]);
// 验证基础数据一致性
if self.inverse_bind_matrices.len() != self.bones.len() {
return Err(Error::InvalidData(format!(
"inverse_bind_matrices and bones length mismatch: {} vs {}",
self.inverse_bind_matrices.len(),
self.bones.len()
)));
}
if !self.vertices_bind.len().is_multiple_of(2) {
return Err(Error::InvalidData(format!(
"vertices_bind length is not even: {}",
self.vertices_bind.len()
)));
}
if !uvs.len().is_multiple_of(2) {
return Err(Error::InvalidData(format!(
"uvs length is not even: {}",
uvs.len()
)));
}
if self.vertices_bind.len() != uvs.len() {
return Err(Error::InvalidData(format!(
"vertices_bind and uvs length mismatch: {} vs {}",
self.vertices_bind.len(),
uvs.len()
)));
}
// 处理 influence 数据(应用默认值逻辑)
let (influence_counts, influence_bone_indices, influence_weights) =
Self::normalize_influences(
self.influence_counts,
self.influence_bone_indices,
self.influence_weights,
vertex_count,
)?;
// 预计算 vertices_locals
let mut vertices_locals = Vec::with_capacity(influence_bone_indices.len() * 2);
let mut influence_offset = 0;
for (v, &count) in influence_counts.iter().enumerate() {
let p_bind = Vec2::new(self.vertices_bind[2 * v], self.vertices_bind[2 * v + 1]);
for _ in 0..count as usize {
let bone_idx = influence_bone_indices[influence_offset] as usize;
let abcdxy = self.inverse_bind_matrices[bone_idx];
// 直接解构并构造 Affine2
let affine = Self::build_affine(abcdxy);
let p_local = affine.transform_point2(p_bind);
// println!("bind {}\naffine {}\nlocal {}", p_bind, affine, p_local);
vertices_locals.push(p_local.x);
vertices_locals.push(p_local.y);
influence_offset += 1;
}
}
Ok(MeshData {
meta: self.meta,
texture_name: self.texture_name,
uvs,
vertex_count,
triangles: self.triangles,
bones: Some(
self.bones
.iter()
.map(|name| {
skeleton_data
.get_bone_index(name)
.ok_or(Error::InvalidData(format!("Invalid bone name: {name}")))
})
.collect::<Result<_, _>>()?,
),
influence_counts,
vertices_locals,
influence_bone_indices,
influence_weights,
clip_end_slot: None,
})
}
/// 将 influence 数据标准化,补全缺失部分
fn normalize_influences(
counts_opt: Option<Vec<u8>>,
indices_opt: Option<Vec<u8>>,
weights_opt: Option<Vec<f32>>,
vertex_count: usize,
) -> Result<Influences, Error> {
// 1. 处理 influence_counts
let counts = match counts_opt {
Some(mut c) => {
if c.len() > vertex_count {
return Err(Error::InvalidData(format!(
"influence_counts too long: needed {} but got {}",
vertex_count,
c.len()
)));
}
// 补全缺失的 count 为 1
c.resize(vertex_count, 1);
c
}
None => vec![1; vertex_count],
};
// 2. 计算总 influence 数量
let total_influences: usize = counts.iter().map(|&c| c as usize).sum();
// 3. 处理 influence_bone_indices
let indices = match indices_opt {
Some(mut idx) => {
if idx.len() > total_influences {
return Err(Error::InvalidData(format!(
"influence_bone_indices too long: needed {} but got {}",
total_influences,
idx.len()
)));
}
// 补全缺失的 indices 为 0
idx.resize(total_influences, 0);
idx
}
None => vec![0; total_influences],
};
// 4. 处理 influence_weights
let weights = match weights_opt {
Some(mut w) => {
if w.len() > total_influences {
return Err(Error::InvalidData(format!(
"influence_weights too long: needed {} but got {}",
total_influences,
w.len()
)));
}
// 补全缺失的 weights 为 1.0
w.resize(total_influences, 1.0);
w
}
None => vec![1.0; total_influences],
};
// 5. 验证最终一致性
if indices.len() != total_influences || weights.len() != total_influences {
return Err(Error::InvalidData(format!(
"influence data length mismatch after normalization: indices: {}, weights: {}, total_influences: {}",
indices.len(),
weights.len(),
total_influences
)));
}
Ok((counts, indices, weights))
}
/// 从 6-float 格式构造 Affine2 矩阵\
/// a, b, x\
/// c, d, y\
/// 0, 0, 1
pub fn build_affine(abcdxy: [f32; 6]) -> Affine2 {
let [a, b, c, d, x, y] = abcdxy;
Affine2::from_mat2_translation(mat2(vec2(a, c), vec2(b, d)), vec2(x, y))
}
}
#[cfg(test)]
mod test_bind_and_ibm {
use glam::{Affine2, vec2};
use crate::{
data::{runtime::skeleton::Skeleton, store::mesh::MeshBindDataStore},
utils::default,
};
#[test]
fn build_affine() {
let abcdxy = [5., 0., 0., 1.5, 2., 4.];
let affine = MeshBindDataStore::build_affine(abcdxy);
println!("{}", affine);
let p = vec2(1., 1.);
let p_notranslate = vec2(5., 1.5);
let p_target = vec2(5. + 2., 1.5 + 4.);
// println!("{}", affine.transform_point2(p));
assert_eq!(affine.transform_point2(p), p_target);
assert_eq!(affine.transform_vector2(p), p_notranslate);
assert_eq!(
affine,
Affine2::from_scale_angle_translation(vec2(5., 1.5), 0., vec2(2., 4.))
);
// 0 2 0
// 1 0 0
// 0 0 1
let abcdxy = [0., 2., 1., 0., 0., 0.];
let affine = MeshBindDataStore::build_affine(abcdxy);
assert_eq!(affine.transform_point2(vec2(3., 5.)), vec2(10., 3.));
}
pub(super) fn create_test_mesh_bind_and_ibm() -> MeshBindDataStore {
MeshBindDataStore {
meta: Default::default(),
// 4个顶点的绑定姿势位置
vertices_bind: vec![
0.0, 0.0, // 顶点0
10.0, 0.0, // 顶点1
10.0, 10.0, // 顶点2(刚体)
0.0, 10.0, // 顶点3(刚体)
],
texture_name: "character_atlas.png".to_string(),
vertex_count: 4,
// UV坐标(对应纹理图集)
uvs: Some(vec![0.0, 0.0, 0.1, 0.0, 0.1, 0.1, 0.0, 0.1]),
// 三角形索引(两个三角形组成四边形)
triangles: vec![0, 1, 2, 0, 2, 3],
// 骨骼列表(3个骨骼:0=主骨骼,1=骨骼1,2=骨骼2)
bones: vec!["root".to_string(), "bone1".to_string(), "bone2".to_string()],
// inverse bind matrices(6-float格式)
inverse_bind_matrices: vec![
[1.0, 0.0, 0.0, 1.0, 0.0, 0.0], // root(单位矩阵)
[1.0, 0.0, 0.0, 1.0, -5.0, 0.0], // bone1(平移x-5,0)
[1.0, 0.0, 0.0, 1.0, 5.0, 0.0], // bone2(平移x+5,0)
],
// 影响数据(只存储前2个顶点的软体数据)
influence_counts: Some(vec![2, 2]), // 顶点0和1各有2个影响
influence_bone_indices: Some(vec![
1, 2, // 顶点0:bone1, bone2
1, 2, // 顶点1:bone1, bone2
]),
influence_weights: Some(vec![
0.3, 0.7, // 顶点0:bone1=0.3, bone2=0.7
0.3, 0.7, // 顶点1:bone1=0.3, bone2=0.7
]),
}
}
#[test]
fn test_meshdata_to_rt() {
let mesh = create_test_mesh_bind_and_ibm();
let mut skeleton_data = Skeleton::new();
let root = skeleton_data.insert_bone("root", default()).unwrap();
let bone1 = skeleton_data.insert_bone("bone1", default()).unwrap();
let bone2 = skeleton_data.insert_bone("bone2", default()).unwrap();
let mesh_rt = mesh
.to_rt(&skeleton_data)
.expect("Conversion to MeshData failed");
assert_eq!(mesh_rt.texture_name, "character_atlas.png");
assert_eq!(mesh_rt.uvs.len(), 8);
assert_eq!(mesh_rt.bones.unwrap(), vec![root, bone1, bone2]);
assert_eq!(mesh_rt.vertices_locals.len(), 12); // 2个2骨骼顶点,2*2=4个坐标,2个刚体顶点各1个坐标,共12个坐标
assert_eq!(mesh_rt.influence_counts, vec![2, 2, 1, 1]);
assert_eq!(mesh_rt.influence_bone_indices, vec![1, 2, 1, 2, 0, 0]);
assert_eq!(
mesh_rt.influence_weights,
vec![0.3, 0.7, 0.3, 0.7, 1.0, 1.0]
);
assert_eq!(mesh_rt.triangles, vec![0, 1, 2, 0, 2, 3]);
// 判断坐标变换是否正确
// 顶点0在bone1和bone2空间的位置
let v0_bone1 = [mesh_rt.vertices_locals[0], mesh_rt.vertices_locals[1]];
let v0_bone2 = [mesh_rt.vertices_locals[2], mesh_rt.vertices_locals[3]];
assert_eq!(v0_bone1, [-5.0, 0.0]); // 0.0+(-5.0)=-5.0
assert_eq!(v0_bone2, [5.0, 0.0]); // 0.0+5.0=5.0
// 顶点1在bone1和bone2空间的位置
let v1_bone1 = [mesh_rt.vertices_locals[4], mesh_rt.vertices_locals[5]];
let v1_bone2 = [mesh_rt.vertices_locals[6], mesh_rt.vertices_locals[7]];
assert_eq!(v1_bone1, [5.0, 0.0]); // (10,0) + (-5,0) = (5,0)
assert_eq!(v1_bone2, [15.0, 0.0]); // (10,0) + (5,0) = (15,0)
// 顶点2(刚体)在root空间的位置
let v2_root = [mesh_rt.vertices_locals[8], mesh_rt.vertices_locals[9]];
assert_eq!(v2_root, [10.0, 10.0]); // (10,10) + (0,0) = (10,10)
// 顶点3(刚体)在root空间的位置
let v3_root = [mesh_rt.vertices_locals[10], mesh_rt.vertices_locals[11]];
assert_eq!(v3_root, [0.0, 10.0]); // (0,10) + (0,0) = (0,10)
}
#[test]
fn load_store() {
let mesh: MeshBindDataStore =
serde_json::from_str(include_str!("../../test_data/mesh1.json")).unwrap();
assert_eq!(mesh, create_test_mesh_bind_and_ibm());
}
}
impl MeshLocalDataStore {
pub fn to_rt(self, skeleton: &Skeleton, slots: &SlotSet) -> Result<MeshData, Error> {
let vertex_count = self.vertex_count as usize;
let uvs = self.uvs.unwrap_or_else(|| vec![0.0; vertex_count * 2]);
// 验证基础数据一致性
if !uvs.len().is_multiple_of(2) {
return Err(Error::InvalidData(format!(
"uvs length is not even: {}",
uvs.len()
)));
}
// 处理 influence 数据(应用默认值逻辑)
let (influence_counts, influence_bone_indices, influence_weights) =
Self::normalize_influences(
self.influence_counts,
self.influence_bone_indices,
self.influence_weights,
vertex_count,
)?;
let total_xy = influence_counts.iter().map(|e| *e as usize).sum::<usize>();
if !self.vertices_locals.len().is_multiple_of(2) {
return Err(Error::InvalidData(format!(
"vertices_locals length is not even: {}",
self.vertices_locals.len()
)));
}
if self.vertices_locals.len() != 2 * total_xy {
return Err(Error::InvalidData(format!(
"vertices_locals and total xy count mismatch: {} vs {}",
self.vertices_locals.len(),
total_xy * 2
)));
}
let bones = {
if let Some(bones) = self.bones {
Some(
bones
.iter()
.map(|name| {
skeleton
.get_bone_index(name)
.ok_or(Error::InvalidData(format!("Invalid bone name: {name}")))
})
.collect::<Result<_, _>>()?,
)
} else {
None
}
};
Ok(MeshData {
meta: self.meta,
texture_name: self.texture_name,
uvs,
vertex_count,
triangles: self.triangles,
bones,
influence_counts,
vertices_locals: self.vertices_locals,
influence_bone_indices,
influence_weights,
clip_end_slot: self
.clip_end_slot
.as_deref()
.map(|name| {
slots.get_index(name).ok_or(Error::InvalidData(format!(
"clip references unknown end slot: {name}"
)))
})
.transpose()?,
})
}
/// 将 influence 数据标准化,补全缺失部分
fn normalize_influences(
counts_opt: Option<Vec<u8>>,
indices_opt: Option<Vec<u8>>,
weights_opt: Option<Vec<f32>>,
vertex_count: usize,
) -> Result<Influences, Error> {
// 1. 处理 influence_counts
let counts = match counts_opt {
Some(mut c) => {
if c.len() > vertex_count {
return Err(Error::InvalidData(format!(
"influence_counts too long: needed {} but got {}",
vertex_count,
c.len()
)));
}
// 补全缺失的 count 为 1
c.resize(vertex_count, 1);
c
}
None => vec![1; vertex_count],
};
// 2. 计算总 influence 数量
let total_influences: usize = counts.iter().map(|&c| c as usize).sum();
// 3. 处理 influence_bone_indices
let indices = match indices_opt {
Some(mut idx) => {
if idx.len() > total_influences {
return Err(Error::InvalidData(format!(
"influence_bone_indices too long: needed {} but got {}",
total_influences,
idx.len()
)));
}
// 补全缺失的 indices 为 0
idx.resize(total_influences, 0);
idx
}
None => vec![0; total_influences],
};
// 4. 处理 influence_weights
let weights = match weights_opt {
Some(mut w) => {
if w.len() > total_influences {
return Err(Error::InvalidData(format!(
"influence_weights too long: needed {} but got {}",
total_influences,
w.len()
)));
}
// 补全缺失的 weights 为 1.0
w.resize(total_influences, 1.0);
w
}
None => vec![1.0; total_influences],
};
// 5. 验证最终一致性
if indices.len() != total_influences || weights.len() != total_influences {
return Err(Error::InvalidData(format!(
"influence data length mismatch after normalization: indices: {}, weights: {}, total_influences: {}",
indices.len(),
weights.len(),
total_influences
)));
}
Ok((counts, indices, weights))
}
}
#[cfg(test)]
mod test_bone_local {
use crate::{
data::{
runtime::skeleton::Skeleton, runtime::slot::SlotSet,
store::mesh::MeshLocalDataStore,
},
utils::default,
};
pub(super) fn create_test_mesh_bone_local() -> MeshLocalDataStore {
MeshLocalDataStore {
meta: Default::default(),
vertex_count: 4,
// 4个顶点的绑定姿势位置
vertices_locals: vec![
0.0, 0.0, // 顶点0
0.0, 2.0, // 顶点0
10.0, 0.0, // 顶点1
13.0, 0.0, // 顶点1
10.0, 10.0, // 顶点2(刚体)
0.0, 10.0, // 顶点3(刚体)
],
texture_name: "character_atlas.png".to_string(),
// UV坐标(对应纹理图集)
uvs: Some(vec![0.0, 0.0, 0.1, 0.0, 0.1, 0.1, 0.0, 0.1]),
// 三角形索引(两个三角形组成四边形)
triangles: vec![0, 1, 2, 0, 2, 3],
// 骨骼列表(3个骨骼:0=主骨骼,1=骨骼1,2=骨骼2)
bones: Some(vec![
"root".to_string(),
"bone1".to_string(),
"bone2".to_string(),
]),
// 影响数据(只存储前2个顶点的软体数据)
influence_counts: Some(vec![2, 2]), // 顶点0和1各有2个影响
influence_bone_indices: Some(vec![
1, 2, // 顶点0:bone1, bone2
1, 2, // 顶点1:bone1, bone2
]),
influence_weights: Some(vec![
0.3, 0.7, // 顶点0:bone1=0.3, bone2=0.7
0.3, 0.7, // 顶点1:bone1=0.3, bone2=0.7
]),
clip_end_slot: None,
}
}
#[test]
fn test_meshdata_to_rt() {
let mesh = create_test_mesh_bone_local();
let mut bone_map = Skeleton::new();
let root = bone_map.insert_bone("root", default()).unwrap();
let bone1 = bone_map.insert_bone("bone1", default()).unwrap();
let bone2 = bone_map.insert_bone("bone2", default()).unwrap();
assert_ne!(root, bone1);
assert_ne!(root, bone2);
assert_ne!(bone1, bone2);
let mesh_rt = mesh
.to_rt(&bone_map, &SlotSet::new())
.expect("Conversion to MeshData failed");
assert_eq!(mesh_rt.texture_name, "character_atlas.png");
assert_eq!(mesh_rt.uvs.len(), 8);
assert_eq!(mesh_rt.bones.unwrap(), vec![root, bone1, bone2]);
assert_eq!(mesh_rt.vertices_locals.len(), 12); // 2个2骨骼顶点,2*2=4个坐标,2个刚体顶点各1个坐标,共12个坐标
assert_eq!(mesh_rt.influence_counts, vec![2, 2, 1, 1]);
assert_eq!(mesh_rt.influence_bone_indices, vec![1, 2, 1, 2, 0, 0]);
assert_eq!(
mesh_rt.influence_weights,
vec![0.3, 0.7, 0.3, 0.7, 1.0, 1.0]
);
assert_eq!(mesh_rt.triangles, vec![0, 1, 2, 0, 2, 3]);
// 顶点0在bone1和bone2空间的位置
let v0_bone1 = [mesh_rt.vertices_locals[0], mesh_rt.vertices_locals[1]];
let v0_bone2 = [mesh_rt.vertices_locals[2], mesh_rt.vertices_locals[3]];
assert_eq!(v0_bone1, [0.0, 0.0]);
assert_eq!(v0_bone2, [0.0, 2.0]);
// 顶点1在bone1和bone2空间的位置
let v1_bone1 = [mesh_rt.vertices_locals[4], mesh_rt.vertices_locals[5]];
let v1_bone2 = [mesh_rt.vertices_locals[6], mesh_rt.vertices_locals[7]];
assert_eq!(v1_bone1, [10.0, 0.0]);
assert_eq!(v1_bone2, [13.0, 0.0]);
// 顶点2(刚体)在root空间的位置
let v2_root = [mesh_rt.vertices_locals[8], mesh_rt.vertices_locals[9]];
assert_eq!(v2_root, [10.0, 10.0]);
// 顶点3(刚体)在root空间的位置
let v3_root = [mesh_rt.vertices_locals[10], mesh_rt.vertices_locals[11]];
assert_eq!(v3_root, [0.0, 10.0]);
}
#[test]
fn load_store() {
// println!("{}", serde_json::to_string(&create_test_mesh()).unwrap());
// return;
let mesh: MeshLocalDataStore =
serde_json::from_str(include_str!("../../test_data/mesh2.json")).unwrap();
assert_eq!(mesh, create_test_mesh_bone_local());
}
}
impl MeshStore {
pub fn to_rt(self, skeleton: &Skeleton, slots: &SlotSet) -> Result<MeshData, Error> {
match self {
MeshStore::BindAndIBM(m) => m.to_rt(skeleton),
MeshStore::BoneLocal(m) => m.to_rt(skeleton, slots),
}
}
}
#[cfg(test)]
mod tests_meshstore {
use crate::{
data::{runtime::skeleton::Skeleton, runtime::slot::SlotSet, store::mesh::MeshStore},
utils::default,
};
use super::{
test_bind_and_ibm::create_test_mesh_bind_and_ibm,
test_bone_local::create_test_mesh_bone_local,
};
#[test]
fn load_store() {
let mut bone_map = Skeleton::new();
let _root = bone_map.insert_bone("root", default()).unwrap();
let _bone1 = bone_map.insert_bone("bone1", default()).unwrap();
let _bone2 = bone_map.insert_bone("bone2", default()).unwrap();
let mesh: MeshStore =
serde_json::from_str(include_str!("../../test_data/mesh1.json")).unwrap();
assert_eq!(mesh, MeshStore::BindAndIBM(create_test_mesh_bind_and_ibm()));
let rt1 = mesh.to_rt(&bone_map, &SlotSet::new()).unwrap();
let mesh: MeshStore =
serde_json::from_str(include_str!("../../test_data/mesh2.json")).unwrap();
assert_eq!(mesh, MeshStore::BoneLocal(create_test_mesh_bone_local()));
let rt2 = mesh.to_rt(&bone_map, &SlotSet::new()).unwrap();
assert_eq!(rt1.texture_name, rt2.texture_name);
assert_eq!(rt1.vertex_count, rt2.vertex_count);
assert_eq!(rt1.uvs, rt2.uvs);
assert_eq!(rt1.triangles, rt2.triangles);
assert_eq!(rt1.bones, rt2.bones);
assert_eq!(rt1.influence_counts, rt2.influence_counts);
assert_eq!(rt1.influence_bone_indices, rt2.influence_bone_indices);
assert_eq!(rt1.influence_weights, rt2.influence_weights);
assert_ne!(rt1.vertices_locals, rt2.vertices_locals);
}
}
}
#[cfg(test)]
mod test_flatten_serde {
use crate::data::{runtime::mesh::MeshMetadata, store::mesh::MeshBindDataStore};
#[test]
fn serde_flatten() {
let data = MeshBindDataStore {
meta: MeshMetadata {
name: Some("nameA".to_string()),
skin: Some("skinA".to_string()),
slot: Some("slotA".to_string()),
},
vertices_bind: vec![],
texture_name: "".to_string(),
uvs: None,
vertex_count: 0,
triangles: vec![],
bones: vec![],
inverse_bind_matrices: vec![],
influence_counts: None,
influence_bone_indices: None,
influence_weights: None,
};
let json = serde_json::to_string(&data).unwrap();
assert_eq!(
json,
r#"{"name":"nameA","slot":"slotA","skin":"skinA","vertices_bind":[],"texture_name":"","vertex_count":0,"triangles":[],"bones":[],"inverse_bind_matrices":[]}"#
);
println!("{}", json);
let data2: MeshBindDataStore = serde_json::from_str(&json).unwrap();
assert_eq!(data, data2);
}
}