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
use encoding_rs::SHIFT_JIS;
use crate::{
mpl::MPLKeyFrame,
utils::{Quaternion, Vector3},
};
use std::io::{Cursor, Write};
fn create_ease_in_out_interpolation() -> [u8; 64] {
let mut interpolation = [0u8; 64];
// Ease-in-out control points - using (20, 107) for pronounced curve
let x1 = 64u8;
let y1 = 0u8;
let x2 = 63u8;
let y2 = 127u8;
// Block 1: X_x1,Y_x1,phy1,phy2, X_y1,Y_y1,Z_y1,R_y1, X_x2,Y_x2,Z_x2,R_x2, X_y2,Y_y2,Z_y2,R_y2
interpolation[0] = x1; // X_x1
interpolation[1] = x1; // Y_x1
interpolation[2] = 0; // phy1 (physics off)
interpolation[3] = 0; // phy2 (physics off)
interpolation[4] = y1; // X_y1
interpolation[5] = y1; // Y_y1
interpolation[6] = y1; // Z_y1
interpolation[7] = y1; // R_y1
interpolation[8] = x2; // X_x2
interpolation[9] = x2; // Y_x2
interpolation[10] = x2; // Z_x2
interpolation[11] = x2; // R_x2
interpolation[12] = y2; // X_y2
interpolation[13] = y2; // Y_y2
interpolation[14] = y2; // Z_y2
interpolation[15] = y2; // R_y2
// Block 2: Y_x1,Z_x1,R_x1,X_y1, Y_y1,Z_y1,R_y1,X_x2, Y_x2,Z_x2,R_x2,X_y2, Y_y2,Z_y2,R_y2,00
interpolation[16] = x1; // Y_x1
interpolation[17] = x1; // Z_x1
interpolation[18] = x1; // R_x1
interpolation[19] = y1; // X_y1
interpolation[20] = y1; // Y_y1
interpolation[21] = y1; // Z_y1
interpolation[22] = y1; // R_y1
interpolation[23] = x2; // X_x2
interpolation[24] = x2; // Y_x2
interpolation[25] = x2; // Z_x2
interpolation[26] = x2; // R_x2
interpolation[27] = y2; // X_y2
interpolation[28] = y2; // Y_y2
interpolation[29] = y2; // Z_y2
interpolation[30] = y2; // R_y2
interpolation[31] = 0; // padding
// Block 3: Z_x1,R_x1,X_y1,Y_y1, Z_y1,R_y1,X_x2,Y_x2, Z_x2,R_x2,X_y2,Y_y2, Z_y2,R_y2,00,00
interpolation[32] = x1; // Z_x1
interpolation[33] = x1; // R_x1
interpolation[34] = y1; // X_y1
interpolation[35] = y1; // Y_y1
interpolation[36] = y1; // Z_y1
interpolation[37] = y1; // R_y1
interpolation[38] = x2; // X_x2
interpolation[39] = x2; // Y_x2
interpolation[40] = x2; // Z_x2
interpolation[41] = x2; // R_x2
interpolation[42] = y2; // X_y2
interpolation[43] = y2; // Y_y2
interpolation[44] = y2; // Z_y2
interpolation[45] = y2; // R_y2
interpolation[46] = 0; // padding
interpolation[47] = 0; // padding
// Block 4: R_x1,X_y1,Y_y1,Z_y1, R_y1,X_x2,Y_x2,Z_x2, R_x2,X_y2,Y_y2,Z_y2, R_y2,00,00,00
interpolation[48] = x1; // R_x1
interpolation[49] = y1; // X_y1
interpolation[50] = y1; // Y_y1
interpolation[51] = y1; // Z_y1
interpolation[52] = y1; // R_y1
interpolation[53] = x2; // X_x2
interpolation[54] = x2; // Y_x2
interpolation[55] = x2; // Z_x2
interpolation[56] = x2; // R_x2
interpolation[57] = y2; // X_y2
interpolation[58] = y2; // Y_y2
interpolation[59] = y2; // Z_y2
interpolation[60] = y2; // R_y2
interpolation[61] = 0; // padding
interpolation[62] = 0; // padding
interpolation[63] = 0; // padding
interpolation
}
#[derive(Debug, Clone)]
pub struct VMDWriter {
pub key_frames: Vec<MPLKeyFrame>,
pub ik_disabled_bones: Vec<String>, // Bones to disable IK for
}
impl VMDWriter {
pub fn new(key_frames: Vec<MPLKeyFrame>) -> Self {
Self {
key_frames,
ik_disabled_bones: vec![
"右足IK親".to_string(),
"左足IK親".to_string(),
"右足IK".to_string(),
"左足IK".to_string(),
"右つま先IK".to_string(),
"左つま先IK".to_string(),
],
}
}
/// Write a bone frame to the buffer
fn write_bone_frame(
cursor: &mut Cursor<Vec<u8>>,
name: &str,
frame: u32,
position: Vector3,
rotation: Quaternion,
) -> Result<(), Box<dyn std::error::Error>> {
// Write bone name (15 bytes)
let (name_bytes, _, _) = SHIFT_JIS.encode(name);
let mut name_buffer = [0u8; 15];
for (i, &byte) in name_bytes.iter().enumerate() {
if i < 15 {
name_buffer[i] = byte;
}
}
cursor.write_all(&name_buffer)?;
// Write frame number (4 bytes, little endian)
cursor.write_all(&frame.to_le_bytes())?;
// Write position (12 bytes: 3 x f32, little endian)
cursor.write_all(&position.x.to_le_bytes())?;
cursor.write_all(&position.y.to_le_bytes())?;
cursor.write_all(&position.z.to_le_bytes())?;
// Write rotation quaternion (16 bytes: 4 x f32, little endian)
cursor.write_all(&rotation.x.to_le_bytes())?;
cursor.write_all(&rotation.y.to_le_bytes())?;
cursor.write_all(&rotation.z.to_le_bytes())?;
cursor.write_all(&rotation.w.to_le_bytes())?;
// Write interpolation parameters (64 bytes, ease-in-out curve)
let interpolation = create_ease_in_out_interpolation();
// let interpolation = [20u8; 64];
cursor.write_all(&interpolation)?;
Ok(())
}
/// Write a morph frame to the buffer
fn write_morph_frame(
cursor: &mut Cursor<Vec<u8>>,
name: &str,
frame: u32,
weight: f32,
) -> Result<(), Box<dyn std::error::Error>> {
// Write morph name (15 bytes)
let (name_bytes, _, _) = SHIFT_JIS.encode(name);
let mut name_buffer = [0u8; 15];
for (i, &byte) in name_bytes.iter().enumerate() {
if i < 15 {
name_buffer[i] = byte;
}
}
cursor.write_all(&name_buffer)?;
// Write frame number (4 bytes, little endian)
cursor.write_all(&frame.to_le_bytes())?;
// Write weight (4 bytes, little endian)
cursor.write_all(&weight.to_le_bytes())?;
Ok(())
}
/// Write a property key frame with IK flags to the buffer
fn write_property_key_frame(
cursor: &mut Cursor<Vec<u8>>,
frame: u32,
ik_states: &[(String, bool)], // (bone_name, ik_enabled)
) -> Result<(), Box<dyn std::error::Error>> {
// Write frame number (4 bytes, little endian)
cursor.write_all(&frame.to_le_bytes())?;
// Write visibility (1 byte, always visible for now)
cursor.write_all(&[1u8])?;
// Write IK state count (4 bytes, little endian)
cursor.write_all(&(ik_states.len() as u32).to_le_bytes())?;
// Write each IK state
for (bone_name, ik_enabled) in ik_states {
// Write bone name (20 bytes)
let (name_bytes, _, _) = SHIFT_JIS.encode(bone_name);
let mut name_buffer = [0u8; 20];
for (i, &byte) in name_bytes.iter().enumerate() {
if i < 20 {
name_buffer[i] = byte;
}
}
cursor.write_all(&name_buffer)?;
// Write IK enabled flag (1 byte)
cursor.write_all(&[if *ik_enabled { 1u8 } else { 0u8 }])?;
}
Ok(())
}
/// Create VMD file data from recorded frames
pub fn create_vmd(&self) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
if self.key_frames.is_empty() {
return Ok(Vec::new());
}
// Count total bone frame entries across all keyframes
let total_bone_frames: u32 = self
.key_frames
.iter()
.map(|kf| kf.bone_frames.len() as u32)
.sum();
// Count total morph frame entries across all keyframes
let total_morph_frames: u32 = self
.key_frames
.iter()
.map(|kf| kf.morph_frames.len() as u32)
.sum();
// Calculate property key frame size (if we have IK disabled bones)
let property_key_frame_count = if !self.ik_disabled_bones.is_empty() {
1
} else {
0
};
let property_key_frame_size = 4 + 1 + 4 + (self.ik_disabled_bones.len() * (20 + 1)); // frame + visibility + ik_count + ik_states
// Calculate sizes
let header_size = 30 + 20; // Header + model name
let bone_frame_size = 15 + 4 + 12 + 16 + 64; // 111 bytes per bone frame
let morph_frame_size = 15 + 4 + 4; // 23 bytes per morph frame
let total_size = header_size + 4 + // bone frame count
(bone_frame_size * total_bone_frames) as usize +
4 + // morph frame count
(morph_frame_size * total_morph_frames) as usize +
4 + // camera keyframe count
4 + // light keyframe count
4 + // self shadow keyframe count
4 + // property keyframe count
(property_key_frame_size * property_key_frame_count) as usize;
let buffer = Vec::with_capacity(total_size);
let mut cursor = Cursor::new(buffer);
// Write header (30 bytes)
let header = "Vocaloid Motion Data 0002";
let mut header_buffer = [0u8; 30];
for (i, byte) in header.bytes().enumerate() {
if i < 30 {
header_buffer[i] = byte;
}
}
cursor.write_all(&header_buffer)?;
// Write model name (20 bytes, empty)
let model_name_buffer = [0u8; 20];
cursor.write_all(&model_name_buffer)?;
// Write bone frame count
cursor.write_all(&total_bone_frames.to_le_bytes())?;
// Write bone frames
for frame in &self.key_frames {
let frame_number = (frame.time * 60.0) as u32; // Convert seconds to frame at 60 fps
for bone_frame in &frame.bone_frames {
Self::write_bone_frame(
&mut cursor,
&bone_frame.name_jp(),
frame_number,
bone_frame.position(),
bone_frame.rotation(),
)?;
}
}
// Write morph frame count
cursor.write_all(&total_morph_frames.to_le_bytes())?;
// Write morph frames
for frame in &self.key_frames {
let frame_number = (frame.time * 60.0) as u32; // Convert seconds to frame at 60 fps
for morph_frame in &frame.morph_frames {
Self::write_morph_frame(
&mut cursor,
&morph_frame.name_jp,
frame_number,
morph_frame.weight,
)?;
}
}
// Write counts for other frame types
cursor.write_all(&0u32.to_le_bytes())?; // Camera keyframe count
cursor.write_all(&0u32.to_le_bytes())?; // Light keyframe count
cursor.write_all(&0u32.to_le_bytes())?; // Self shadow keyframe count
// Write property keyframe count
cursor.write_all(&property_key_frame_count.to_le_bytes())?;
// Write property keyframes (for IK flags)
if !self.ik_disabled_bones.is_empty() {
let frame_number = 0; // Write at frame 0
let ik_states: Vec<(String, bool)> = self
.ik_disabled_bones
.iter()
.map(|bone_name| (bone_name.clone(), false))
.collect();
Self::write_property_key_frame(&mut cursor, frame_number, &ik_states)?;
}
Ok(cursor.into_inner())
}
}