@babylonjs/serializers 8.25.2 → 8.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,10 +3,11 @@ export declare class BVHExporter {
3
3
  static Export(skeleton: Skeleton, animationNames?: string[], frameRate?: number): string;
4
4
  private static _BuildBoneHierarchy;
5
5
  private static _ExportHierarchy;
6
+ private static _IsEndSite;
6
7
  private static _GetBoneOffset;
7
8
  private static _ExportMotionData;
8
9
  private static _CollectFrameValues;
9
- private static _GetPositionAtFrame;
10
- private static _GetRotationAtFrame;
10
+ private static _GetPositionAtFrameIndex;
11
+ private static _GetRotationAtFrameIndex;
11
12
  private static _QuaternionToEuler;
12
13
  }
@@ -1,31 +1,53 @@
1
+ import { AnimationRange } from "@babylonjs/core/Animations/animationRange.js";
1
2
  import { Vector3, Quaternion, Matrix } from "@babylonjs/core/Maths/math.vector.js";
2
3
  import { Tools } from "@babylonjs/core/Misc/tools.js";
3
4
  import { Epsilon } from "@babylonjs/core/Maths/math.constants.js";
4
5
  export class BVHExporter {
5
- static Export(skeleton, animationNames = [], frameRate = 0.03333) {
6
+ static Export(skeleton, animationNames = [], frameRate) {
7
+ // Validate skeleton
8
+ if (!skeleton || skeleton.bones.length === 0) {
9
+ throw new Error("Invalid or empty skeleton provided");
10
+ }
6
11
  // If no animation names provided, use all available animations
7
12
  let animationsToExport = animationNames;
8
13
  if (!animationNames || animationNames.length === 0) {
9
14
  animationsToExport = skeleton.animations.map((anim) => anim.name);
10
15
  }
11
- // Get animation range from the first animation (or create a default one)
12
- let animationRange = null;
13
- if (animationsToExport.length > 0) {
14
- animationRange = skeleton.getAnimationRange(animationsToExport[0]);
16
+ // Calculate overall animation range from all specified animations
17
+ let overallRange = null;
18
+ for (const animName of animationsToExport) {
19
+ const range = skeleton.getAnimationRange(animName);
20
+ if (range) {
21
+ overallRange = overallRange ? new AnimationRange("animation-range", Math.min(overallRange.from, range.from), Math.max(overallRange.to, range.to)) : range;
22
+ }
15
23
  }
16
- if (!animationRange) {
17
- // If no animation range found, create a default one or throw error
24
+ if (!overallRange) {
25
+ // If no animation range found, try to get from any animation
18
26
  if (skeleton.animations.length > 0) {
19
- // Use the first available animation
20
- animationRange = skeleton.getAnimationRange(skeleton.animations[0].name);
27
+ overallRange = skeleton.getAnimationRange(skeleton.animations[0].name);
21
28
  }
22
- if (!animationRange) {
29
+ if (!overallRange) {
23
30
  throw new Error("No animation range found in skeleton");
24
31
  }
25
32
  }
33
+ // Calculate frame rate from animation data if not provided
34
+ const actualFrameRate = frameRate || 1 / (skeleton.animations[0]?.framePerSecond || 30);
26
35
  // Build bone hierarchy and collect animation data
27
36
  const boneHierarchy = this._BuildBoneHierarchy(skeleton, animationsToExport);
28
- const frameCount = animationRange.to - animationRange.from + 1;
37
+ // Calculate frame count from actual animation keyframes
38
+ let frameCount = 0;
39
+ for (const boneData of boneHierarchy) {
40
+ if (boneData.positionKeys.length > 0) {
41
+ frameCount = Math.max(frameCount, boneData.positionKeys.length);
42
+ }
43
+ if (boneData.rotationKeys.length > 0) {
44
+ frameCount = Math.max(frameCount, boneData.rotationKeys.length);
45
+ }
46
+ }
47
+ // Fallback: if no keyframes found, calculate from time range
48
+ if (frameCount === 0) {
49
+ frameCount = Math.floor((overallRange.to - overallRange.from) / actualFrameRate) + 1;
50
+ }
29
51
  let exportString = "";
30
52
  exportString += `HIERARCHY\n`;
31
53
  // Export hierarchy recursively
@@ -33,10 +55,9 @@ export class BVHExporter {
33
55
  // Export motion data
34
56
  exportString += `MOTION\n`;
35
57
  exportString += `Frames: ${frameCount}\n`;
36
- exportString += `Frame Time: ${frameRate}\n`;
37
- exportString += `\n`;
58
+ exportString += `Frame Time: ${actualFrameRate.toFixed(6)}\n`;
38
59
  // Export frame data
39
- exportString += this._ExportMotionData(boneHierarchy, frameCount, animationRange.from, animationsToExport);
60
+ exportString += this._ExportMotionData(boneHierarchy, frameCount, 0, animationsToExport);
40
61
  return exportString;
41
62
  }
42
63
  static _BuildBoneHierarchy(skeleton, animationNames) {
@@ -64,11 +85,11 @@ export class BVHExporter {
64
85
  if (animationNames.includes(animation.name)) {
65
86
  if (animation.targetProperty === "position") {
66
87
  boneData.hasPositionChannels = true;
67
- boneData.positionKeys = animation.getKeys();
88
+ boneData.positionKeys.push(...animation.getKeys());
68
89
  }
69
90
  else if (animation.targetProperty === "rotationQuaternion") {
70
91
  boneData.hasRotationChannels = true;
71
- boneData.rotationKeys = animation.getKeys();
92
+ boneData.rotationKeys.push(...animation.getKeys());
72
93
  }
73
94
  }
74
95
  }
@@ -88,12 +109,11 @@ export class BVHExporter {
88
109
  }
89
110
  static _ExportHierarchy(boneData, indentLevel) {
90
111
  let result = "";
91
- // 4 spaces identation for each level
92
112
  const indent = " ".repeat(indentLevel);
93
113
  for (const data of boneData) {
94
114
  const bone = data.bone;
95
- // Determine if this is an end site (bone with no children and no animations)
96
- if (data.children.length === 0 && !data.hasPositionChannels && !data.hasRotationChannels) {
115
+ const isEndSite = this._IsEndSite(data);
116
+ if (isEndSite) {
97
117
  result += `${indent}End Site\n`;
98
118
  result += `${indent}{\n`;
99
119
  const offset = this._GetBoneOffset(bone);
@@ -101,7 +121,7 @@ export class BVHExporter {
101
121
  result += `${indent}}\n`;
102
122
  }
103
123
  else {
104
- result += `${indent}JOINT ${bone.name}\n`;
124
+ result += `${indentLevel === 0 ? "ROOT" : `${indent}JOINT`} ${bone.name}\n`;
105
125
  result += `${indent}{\n`;
106
126
  const offset = this._GetBoneOffset(bone);
107
127
  result += `${indent} OFFSET ${offset.x.toFixed(6)} ${offset.y.toFixed(6)} ${offset.z.toFixed(6)}\n`;
@@ -111,37 +131,40 @@ export class BVHExporter {
111
131
  channels.push("Xposition", "Yposition", "Zposition");
112
132
  }
113
133
  if (data.hasRotationChannels) {
114
- // BVH uses ZYX rotation order
134
+ // BVH typically uses ZXY rotation order
115
135
  channels.push("Zrotation", "Xrotation", "Yrotation");
116
136
  }
117
137
  if (channels.length > 0) {
118
138
  result += `${indent} CHANNELS ${channels.length} ${channels.join(" ")}\n`;
119
139
  }
140
+ // Export children recursively
141
+ if (data.children.length > 0) {
142
+ result += this._ExportHierarchy(data.children, indentLevel + 1);
143
+ }
120
144
  result += `${indent}}\n`;
121
145
  }
122
- // Export children recursively
123
- if (data.children.length > 0) {
124
- result += this._ExportHierarchy(data.children, indentLevel + 1);
125
- }
126
146
  }
127
147
  return result;
128
148
  }
149
+ static _IsEndSite(data) {
150
+ // An end site is a bone with no children (regardless of animation channels)
151
+ return data.children.length === 0;
152
+ }
129
153
  static _GetBoneOffset(bone) {
130
- // Get the local offset of the bone from its parent
131
- const parent = bone.getParent();
132
- if (!parent) {
133
- return Vector3.Zero(); // Root bone
134
- }
135
- // For BVH, we need to get the bone's offset from its parent
136
- // This should match the original BVH file's OFFSET values
137
- const boneMatrix = bone.getBindMatrix();
138
- const parentMatrix = parent.getBindMatrix();
139
- // Calculate the relative position
140
- const bonePosition = boneMatrix.getTranslation();
141
- const parentPosition = parentMatrix.getTranslation();
142
- const relativeOffset = bonePosition.subtract(parentPosition);
143
- // Return the full 3D offset
144
- return relativeOffset;
154
+ // Use the bone's rest matrix or local matrix for correct offset
155
+ try {
156
+ if (!bone.getParent()) {
157
+ // Root bone - use rest matrix translation
158
+ return bone.getRestMatrix().getTranslation();
159
+ }
160
+ // For child bones, use local matrix translation
161
+ const localMatrix = bone.getLocalMatrix();
162
+ return localMatrix.getTranslation();
163
+ }
164
+ catch (error) {
165
+ // Fallback to zero offset if matrix operations fail
166
+ return Vector3.Zero();
167
+ }
145
168
  }
146
169
  static _ExportMotionData(boneData, frameCount, startFrame, animationNames) {
147
170
  let result = "";
@@ -153,82 +176,62 @@ export class BVHExporter {
153
176
  }
154
177
  return result;
155
178
  }
156
- static _CollectFrameValues(boneData, frame, values, animationNames) {
179
+ static _CollectFrameValues(boneData, frameIndex, values, animationNames) {
157
180
  for (const data of boneData) {
158
181
  // Skip end sites
159
- if (data.children.length === 0 && !data.hasPositionChannels && !data.hasRotationChannels) {
182
+ if (this._IsEndSite(data)) {
160
183
  continue;
161
184
  }
162
185
  // Add position values if available
163
186
  if (data.hasPositionChannels) {
164
- const position = this._GetPositionAtFrame(data.positionKeys, frame);
187
+ const position = this._GetPositionAtFrameIndex(data.positionKeys, frameIndex);
165
188
  values.push(position.x, position.y, position.z);
166
189
  }
167
190
  // Add rotation values if available
168
191
  if (data.hasRotationChannels) {
169
- const rotation = this._GetRotationAtFrame(data.rotationKeys, frame);
170
- // Convert to Euler angles in ZYX order
192
+ const rotation = this._GetRotationAtFrameIndex(data.rotationKeys, frameIndex);
193
+ // Convert to Euler angles in ZXY order
171
194
  const euler = this._QuaternionToEuler(rotation);
172
195
  values.push(euler.z, euler.x, euler.y);
173
196
  }
174
197
  // Process children recursively
175
198
  if (data.children.length > 0) {
176
- this._CollectFrameValues(data.children, frame, values, animationNames);
199
+ this._CollectFrameValues(data.children, frameIndex, values, animationNames);
177
200
  }
178
201
  }
179
202
  }
180
- static _GetPositionAtFrame(keys, frame) {
203
+ static _GetPositionAtFrameIndex(keys, frameIndex) {
181
204
  if (keys.length === 0) {
182
205
  return Vector3.Zero();
183
206
  }
184
- // Find the appropriate key or interpolate
185
- let key1 = keys[0];
186
- let key2 = keys[keys.length - 1];
187
- for (let i = 0; i < keys.length - 1; i++) {
188
- if (keys[i].frame <= frame && keys[i + 1].frame >= frame) {
189
- key1 = keys[i];
190
- key2 = keys[i + 1];
191
- break;
192
- }
193
- }
194
- if (key1.frame === key2.frame) {
195
- return key1.value.clone();
196
- }
197
- const t = (frame - key1.frame) / (key2.frame - key1.frame);
198
- return Vector3.Lerp(key1.value, key2.value, t);
207
+ // Clamp frame index to valid range
208
+ const clampedIndex = Math.max(0, Math.min(frameIndex, keys.length - 1));
209
+ return keys[clampedIndex].value.clone();
199
210
  }
200
- static _GetRotationAtFrame(keys, frame) {
211
+ static _GetRotationAtFrameIndex(keys, frameIndex) {
201
212
  if (keys.length === 0) {
202
213
  return Quaternion.Identity();
203
214
  }
204
- // Find the appropriate key or interpolate
205
- let key1 = keys[0];
206
- let key2 = keys[keys.length - 1];
207
- for (let i = 0; i < keys.length - 1; i++) {
208
- if (keys[i].frame <= frame && keys[i + 1].frame >= frame) {
209
- key1 = keys[i];
210
- key2 = keys[i + 1];
211
- break;
212
- }
213
- }
214
- if (key1.frame === key2.frame) {
215
- return key1.value.clone();
216
- }
217
- const t = (frame - key1.frame) / (key2.frame - key1.frame);
218
- return Quaternion.Slerp(key1.value, key2.value, t);
215
+ // Clamp frame index to valid range
216
+ const clampedIndex = Math.max(0, Math.min(frameIndex, keys.length - 1));
217
+ return keys[clampedIndex].value.clone();
219
218
  }
220
219
  static _QuaternionToEuler(quaternion) {
221
- // Convert quaternion to Euler angles in ZYX order
222
- const matrix = quaternion.toRotationMatrix(new Matrix());
220
+ // Convert quaternion to Euler angles in ZXY order for BVH
221
+ const matrix = new Matrix();
222
+ quaternion.toRotationMatrix(matrix);
223
+ const m = matrix.m;
223
224
  let x, y, z;
224
- if (Math.abs(matrix.m[6]) < 1 - Epsilon) {
225
- x = Math.atan2(-matrix.m[7], matrix.m[8]);
226
- y = Math.asin(matrix.m[6]);
227
- z = Math.atan2(-matrix.m[3], matrix.m[0]);
225
+ // ZXY rotation order extraction
226
+ const sy = Math.sqrt(m[0] * m[0] + m[1] * m[1]);
227
+ if (sy > Epsilon) {
228
+ x = Math.atan2(m[6], m[10]);
229
+ y = Math.atan2(-m[2], sy);
230
+ z = Math.atan2(m[1], m[0]);
228
231
  }
229
232
  else {
230
- x = Math.atan2(matrix.m[5], matrix.m[4]);
231
- y = Math.asin(matrix.m[6]);
233
+ x = Math.atan2(-m[9], m[5]);
234
+ y = Math.atan2(-m[2], sy);
232
235
  z = 0;
233
236
  }
234
237
  return new Vector3(Tools.ToDegrees(x), Tools.ToDegrees(y), Tools.ToDegrees(z));
@@ -1 +1 @@
1
- {"version":3,"file":"bvhSerializer.js","sourceRoot":"","sources":["../../../../dev/serializers/src/BVH/bvhSerializer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Skeleton } from \"core/Bones/skeleton\";\nimport type { Bone } from \"core/Bones/bone\";\nimport type { IAnimationKey } from \"core/Animations\";\nimport { Vector3, Quaternion, Matrix } from \"core/Maths/math.vector\";\nimport { Tools } from \"core/Misc/tools\";\nimport { Epsilon } from \"core/Maths/math.constants\";\n\ninterface IBVHBoneData {\n bone: Bone;\n children: IBVHBoneData[];\n hasPositionChannels: boolean;\n hasRotationChannels: boolean;\n positionKeys: IAnimationKey[];\n rotationKeys: IAnimationKey[];\n}\n\nexport class BVHExporter {\n public static Export(skeleton: Skeleton, animationNames: string[] = [], frameRate: number = 0.03333): string {\n // If no animation names provided, use all available animations\n let animationsToExport = animationNames;\n if (!animationNames || animationNames.length === 0) {\n animationsToExport = skeleton.animations.map((anim) => anim.name);\n }\n\n // Get animation range from the first animation (or create a default one)\n let animationRange = null;\n if (animationsToExport.length > 0) {\n animationRange = skeleton.getAnimationRange(animationsToExport[0]);\n }\n\n if (!animationRange) {\n // If no animation range found, create a default one or throw error\n if (skeleton.animations.length > 0) {\n // Use the first available animation\n animationRange = skeleton.getAnimationRange(skeleton.animations[0].name);\n }\n\n if (!animationRange) {\n throw new Error(\"No animation range found in skeleton\");\n }\n }\n\n // Build bone hierarchy and collect animation data\n const boneHierarchy = this._BuildBoneHierarchy(skeleton, animationsToExport);\n const frameCount = animationRange.to - animationRange.from + 1;\n\n let exportString = \"\";\n exportString += `HIERARCHY\\n`;\n\n // Export hierarchy recursively\n exportString += this._ExportHierarchy(boneHierarchy, 0);\n\n // Export motion data\n exportString += `MOTION\\n`;\n exportString += `Frames: ${frameCount}\\n`;\n exportString += `Frame Time: ${frameRate}\\n`;\n exportString += `\\n`;\n\n // Export frame data\n exportString += this._ExportMotionData(boneHierarchy, frameCount, animationRange.from, animationsToExport);\n\n return exportString;\n }\n\n private static _BuildBoneHierarchy(skeleton: Skeleton, animationNames: string[]): IBVHBoneData[] {\n const boneMap = new Map<Bone, IBVHBoneData>();\n const rootBones: IBVHBoneData[] = [];\n\n // First pass: create bone data objects\n for (const bone of skeleton.bones) {\n const boneData: IBVHBoneData = {\n bone,\n children: [],\n hasPositionChannels: false,\n hasRotationChannels: false,\n positionKeys: [],\n rotationKeys: [],\n };\n boneMap.set(bone, boneData);\n }\n\n // Second pass: build hierarchy and collect animation data\n for (const bone of skeleton.bones) {\n const boneData = boneMap.get(bone)!;\n\n // Check if bone has animations from the specified animation names\n if (bone.animations.length > 0) {\n for (const animation of bone.animations) {\n // Only include animations that are in the specified animation names\n if (animationNames.includes(animation.name)) {\n if (animation.targetProperty === \"position\") {\n boneData.hasPositionChannels = true;\n boneData.positionKeys = animation.getKeys();\n } else if (animation.targetProperty === \"rotationQuaternion\") {\n boneData.hasRotationChannels = true;\n boneData.rotationKeys = animation.getKeys();\n }\n }\n }\n }\n\n // Build hierarchy\n if (bone.getParent()) {\n const parentData = boneMap.get(bone.getParent()!);\n if (parentData) {\n parentData.children.push(boneData);\n }\n } else {\n rootBones.push(boneData);\n }\n }\n\n return rootBones;\n }\n\n private static _ExportHierarchy(boneData: IBVHBoneData[], indentLevel: number): string {\n let result = \"\";\n // 4 spaces identation for each level\n const indent = \" \".repeat(indentLevel);\n\n for (const data of boneData) {\n const bone = data.bone;\n\n // Determine if this is an end site (bone with no children and no animations)\n if (data.children.length === 0 && !data.hasPositionChannels && !data.hasRotationChannels) {\n result += `${indent}End Site\\n`;\n result += `${indent}{\\n`;\n const offset = this._GetBoneOffset(bone);\n result += `${indent} OFFSET ${offset.x.toFixed(6)} ${offset.y.toFixed(6)} ${offset.z.toFixed(6)}\\n`;\n result += `${indent}}\\n`;\n } else {\n result += `${indent}JOINT ${bone.name}\\n`;\n result += `${indent}{\\n`;\n const offset = this._GetBoneOffset(bone);\n result += `${indent} OFFSET ${offset.x.toFixed(6)} ${offset.y.toFixed(6)} ${offset.z.toFixed(6)}\\n`;\n\n // Determine channels\n const channels: string[] = [];\n if (data.hasPositionChannels) {\n channels.push(\"Xposition\", \"Yposition\", \"Zposition\");\n }\n if (data.hasRotationChannels) {\n // BVH uses ZYX rotation order\n channels.push(\"Zrotation\", \"Xrotation\", \"Yrotation\");\n }\n\n if (channels.length > 0) {\n result += `${indent} CHANNELS ${channels.length} ${channels.join(\" \")}\\n`;\n }\n\n result += `${indent}}\\n`;\n }\n\n // Export children recursively\n if (data.children.length > 0) {\n result += this._ExportHierarchy(data.children, indentLevel + 1);\n }\n }\n\n return result;\n }\n\n private static _GetBoneOffset(bone: Bone): Vector3 {\n // Get the local offset of the bone from its parent\n const parent = bone.getParent();\n if (!parent) {\n return Vector3.Zero(); // Root bone\n }\n\n // For BVH, we need to get the bone's offset from its parent\n // This should match the original BVH file's OFFSET values\n const boneMatrix = bone.getBindMatrix();\n const parentMatrix = parent.getBindMatrix();\n\n // Calculate the relative position\n const bonePosition = boneMatrix.getTranslation();\n const parentPosition = parentMatrix.getTranslation();\n const relativeOffset = bonePosition.subtract(parentPosition);\n\n // Return the full 3D offset\n return relativeOffset;\n }\n\n private static _ExportMotionData(boneData: IBVHBoneData[], frameCount: number, startFrame: number, animationNames: string[]): string {\n let result = \"\";\n\n for (let frame = 0; frame < frameCount; frame++) {\n const frameValues: number[] = [];\n\n // Collect values for all bones in hierarchy order\n this._CollectFrameValues(boneData, frame + startFrame, frameValues, animationNames);\n\n result += frameValues.map((v) => v.toFixed(6)).join(\" \") + \"\\n\";\n }\n\n return result;\n }\n\n private static _CollectFrameValues(boneData: IBVHBoneData[], frame: number, values: number[], animationNames: string[]): void {\n for (const data of boneData) {\n // Skip end sites\n if (data.children.length === 0 && !data.hasPositionChannels && !data.hasRotationChannels) {\n continue;\n }\n\n // Add position values if available\n if (data.hasPositionChannels) {\n const position = this._GetPositionAtFrame(data.positionKeys, frame);\n values.push(position.x, position.y, position.z);\n }\n\n // Add rotation values if available\n if (data.hasRotationChannels) {\n const rotation = this._GetRotationAtFrame(data.rotationKeys, frame);\n // Convert to Euler angles in ZYX order\n const euler = this._QuaternionToEuler(rotation);\n values.push(euler.z, euler.x, euler.y);\n }\n\n // Process children recursively\n if (data.children.length > 0) {\n this._CollectFrameValues(data.children, frame, values, animationNames);\n }\n }\n }\n\n private static _GetPositionAtFrame(keys: IAnimationKey[], frame: number): Vector3 {\n if (keys.length === 0) {\n return Vector3.Zero();\n }\n\n // Find the appropriate key or interpolate\n let key1 = keys[0];\n let key2 = keys[keys.length - 1];\n\n for (let i = 0; i < keys.length - 1; i++) {\n if (keys[i].frame <= frame && keys[i + 1].frame >= frame) {\n key1 = keys[i];\n key2 = keys[i + 1];\n break;\n }\n }\n\n if (key1.frame === key2.frame) {\n return key1.value.clone();\n }\n\n const t = (frame - key1.frame) / (key2.frame - key1.frame);\n return Vector3.Lerp(key1.value, key2.value, t);\n }\n\n private static _GetRotationAtFrame(keys: IAnimationKey[], frame: number): Quaternion {\n if (keys.length === 0) {\n return Quaternion.Identity();\n }\n\n // Find the appropriate key or interpolate\n let key1 = keys[0];\n let key2 = keys[keys.length - 1];\n\n for (let i = 0; i < keys.length - 1; i++) {\n if (keys[i].frame <= frame && keys[i + 1].frame >= frame) {\n key1 = keys[i];\n key2 = keys[i + 1];\n break;\n }\n }\n\n if (key1.frame === key2.frame) {\n return key1.value.clone();\n }\n\n const t = (frame - key1.frame) / (key2.frame - key1.frame);\n return Quaternion.Slerp(key1.value, key2.value, t);\n }\n\n private static _QuaternionToEuler(quaternion: Quaternion): Vector3 {\n // Convert quaternion to Euler angles in ZYX order\n const matrix = quaternion.toRotationMatrix(new Matrix());\n\n let x, y, z;\n\n if (Math.abs(matrix.m[6]) < 1 - Epsilon) {\n x = Math.atan2(-matrix.m[7], matrix.m[8]);\n y = Math.asin(matrix.m[6]);\n z = Math.atan2(-matrix.m[3], matrix.m[0]);\n } else {\n x = Math.atan2(matrix.m[5], matrix.m[4]);\n y = Math.asin(matrix.m[6]);\n z = 0;\n }\n\n return new Vector3(Tools.ToDegrees(x), Tools.ToDegrees(y), Tools.ToDegrees(z));\n }\n}\n"]}
1
+ {"version":3,"file":"bvhSerializer.js","sourceRoot":"","sources":["../../../../dev/serializers/src/BVH/bvhSerializer.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Skeleton } from \"core/Bones/skeleton\";\nimport type { Bone } from \"core/Bones/bone\";\nimport type { IAnimationKey } from \"core/Animations/animationKey\";\nimport { AnimationRange } from \"core/Animations/animationRange\";\nimport { Vector3, Quaternion, Matrix } from \"core/Maths/math.vector\";\nimport { Tools } from \"core/Misc/tools\";\nimport { Epsilon } from \"core/Maths/math.constants\";\nimport type { Nullable } from \"core/types\";\n\ninterface IBVHBoneData {\n bone: Bone;\n children: IBVHBoneData[];\n hasPositionChannels: boolean;\n hasRotationChannels: boolean;\n positionKeys: IAnimationKey[];\n rotationKeys: IAnimationKey[];\n}\n\nexport class BVHExporter {\n public static Export(skeleton: Skeleton, animationNames: string[] = [], frameRate?: number): string {\n // Validate skeleton\n if (!skeleton || skeleton.bones.length === 0) {\n throw new Error(\"Invalid or empty skeleton provided\");\n }\n\n // If no animation names provided, use all available animations\n let animationsToExport = animationNames;\n if (!animationNames || animationNames.length === 0) {\n animationsToExport = skeleton.animations.map((anim) => anim.name);\n }\n\n // Calculate overall animation range from all specified animations\n let overallRange: Nullable<AnimationRange> = null;\n for (const animName of animationsToExport) {\n const range = skeleton.getAnimationRange(animName);\n if (range) {\n overallRange = overallRange ? new AnimationRange(\"animation-range\", Math.min(overallRange.from, range.from), Math.max(overallRange.to, range.to)) : range;\n }\n }\n\n if (!overallRange) {\n // If no animation range found, try to get from any animation\n if (skeleton.animations.length > 0) {\n overallRange = skeleton.getAnimationRange(skeleton.animations[0].name);\n }\n\n if (!overallRange) {\n throw new Error(\"No animation range found in skeleton\");\n }\n }\n\n // Calculate frame rate from animation data if not provided\n const actualFrameRate = frameRate || 1 / (skeleton.animations[0]?.framePerSecond || 30);\n\n // Build bone hierarchy and collect animation data\n const boneHierarchy = this._BuildBoneHierarchy(skeleton, animationsToExport);\n\n // Calculate frame count from actual animation keyframes\n let frameCount = 0;\n for (const boneData of boneHierarchy) {\n if (boneData.positionKeys.length > 0) {\n frameCount = Math.max(frameCount, boneData.positionKeys.length);\n }\n if (boneData.rotationKeys.length > 0) {\n frameCount = Math.max(frameCount, boneData.rotationKeys.length);\n }\n }\n\n // Fallback: if no keyframes found, calculate from time range\n if (frameCount === 0) {\n frameCount = Math.floor((overallRange.to - overallRange.from) / actualFrameRate) + 1;\n }\n\n let exportString = \"\";\n exportString += `HIERARCHY\\n`;\n\n // Export hierarchy recursively\n exportString += this._ExportHierarchy(boneHierarchy, 0);\n\n // Export motion data\n exportString += `MOTION\\n`;\n exportString += `Frames: ${frameCount}\\n`;\n exportString += `Frame Time: ${actualFrameRate.toFixed(6)}\\n`;\n\n // Export frame data\n exportString += this._ExportMotionData(boneHierarchy, frameCount, 0, animationsToExport);\n\n return exportString;\n }\n\n private static _BuildBoneHierarchy(skeleton: Skeleton, animationNames: string[]): IBVHBoneData[] {\n const boneMap = new Map<Bone, IBVHBoneData>();\n const rootBones: IBVHBoneData[] = [];\n\n // First pass: create bone data objects\n for (const bone of skeleton.bones) {\n const boneData: IBVHBoneData = {\n bone,\n children: [],\n hasPositionChannels: false,\n hasRotationChannels: false,\n positionKeys: [],\n rotationKeys: [],\n };\n boneMap.set(bone, boneData);\n }\n\n // Second pass: build hierarchy and collect animation data\n for (const bone of skeleton.bones) {\n const boneData = boneMap.get(bone)!;\n\n // Check if bone has animations from the specified animation names\n if (bone.animations.length > 0) {\n for (const animation of bone.animations) {\n // Only include animations that are in the specified animation names\n if (animationNames.includes(animation.name)) {\n if (animation.targetProperty === \"position\") {\n boneData.hasPositionChannels = true;\n boneData.positionKeys.push(...animation.getKeys());\n } else if (animation.targetProperty === \"rotationQuaternion\") {\n boneData.hasRotationChannels = true;\n boneData.rotationKeys.push(...animation.getKeys());\n }\n }\n }\n }\n\n // Build hierarchy\n if (bone.getParent()) {\n const parentData = boneMap.get(bone.getParent()!);\n if (parentData) {\n parentData.children.push(boneData);\n }\n } else {\n rootBones.push(boneData);\n }\n }\n\n return rootBones;\n }\n\n private static _ExportHierarchy(boneData: IBVHBoneData[], indentLevel: number): string {\n let result = \"\";\n const indent = \" \".repeat(indentLevel);\n\n for (const data of boneData) {\n const bone = data.bone;\n const isEndSite = this._IsEndSite(data);\n\n if (isEndSite) {\n result += `${indent}End Site\\n`;\n result += `${indent}{\\n`;\n const offset = this._GetBoneOffset(bone);\n result += `${indent} OFFSET ${offset.x.toFixed(6)} ${offset.y.toFixed(6)} ${offset.z.toFixed(6)}\\n`;\n result += `${indent}}\\n`;\n } else {\n result += `${indentLevel === 0 ? \"ROOT\" : `${indent}JOINT`} ${bone.name}\\n`;\n result += `${indent}{\\n`;\n const offset = this._GetBoneOffset(bone);\n result += `${indent} OFFSET ${offset.x.toFixed(6)} ${offset.y.toFixed(6)} ${offset.z.toFixed(6)}\\n`;\n\n // Determine channels\n const channels: string[] = [];\n if (data.hasPositionChannels) {\n channels.push(\"Xposition\", \"Yposition\", \"Zposition\");\n }\n if (data.hasRotationChannels) {\n // BVH typically uses ZXY rotation order\n channels.push(\"Zrotation\", \"Xrotation\", \"Yrotation\");\n }\n\n if (channels.length > 0) {\n result += `${indent} CHANNELS ${channels.length} ${channels.join(\" \")}\\n`;\n }\n\n // Export children recursively\n if (data.children.length > 0) {\n result += this._ExportHierarchy(data.children, indentLevel + 1);\n }\n\n result += `${indent}}\\n`;\n }\n }\n\n return result;\n }\n\n private static _IsEndSite(data: IBVHBoneData): boolean {\n // An end site is a bone with no children (regardless of animation channels)\n return data.children.length === 0;\n }\n\n private static _GetBoneOffset(bone: Bone): Vector3 {\n // Use the bone's rest matrix or local matrix for correct offset\n try {\n if (!bone.getParent()) {\n // Root bone - use rest matrix translation\n return bone.getRestMatrix().getTranslation();\n }\n\n // For child bones, use local matrix translation\n const localMatrix = bone.getLocalMatrix();\n return localMatrix.getTranslation();\n } catch (error) {\n // Fallback to zero offset if matrix operations fail\n return Vector3.Zero();\n }\n }\n\n private static _ExportMotionData(boneData: IBVHBoneData[], frameCount: number, startFrame: number, animationNames: string[]): string {\n let result = \"\";\n\n for (let frame = 0; frame < frameCount; frame++) {\n const frameValues: number[] = [];\n\n // Collect values for all bones in hierarchy order\n this._CollectFrameValues(boneData, frame + startFrame, frameValues, animationNames);\n\n result += frameValues.map((v) => v.toFixed(6)).join(\" \") + \"\\n\";\n }\n\n return result;\n }\n\n private static _CollectFrameValues(boneData: IBVHBoneData[], frameIndex: number, values: number[], animationNames: string[]): void {\n for (const data of boneData) {\n // Skip end sites\n if (this._IsEndSite(data)) {\n continue;\n }\n\n // Add position values if available\n if (data.hasPositionChannels) {\n const position = this._GetPositionAtFrameIndex(data.positionKeys, frameIndex);\n values.push(position.x, position.y, position.z);\n }\n\n // Add rotation values if available\n if (data.hasRotationChannels) {\n const rotation = this._GetRotationAtFrameIndex(data.rotationKeys, frameIndex);\n // Convert to Euler angles in ZXY order\n const euler = this._QuaternionToEuler(rotation);\n values.push(euler.z, euler.x, euler.y);\n }\n\n // Process children recursively\n if (data.children.length > 0) {\n this._CollectFrameValues(data.children, frameIndex, values, animationNames);\n }\n }\n }\n\n private static _GetPositionAtFrameIndex(keys: IAnimationKey[], frameIndex: number): Vector3 {\n if (keys.length === 0) {\n return Vector3.Zero();\n }\n\n // Clamp frame index to valid range\n const clampedIndex = Math.max(0, Math.min(frameIndex, keys.length - 1));\n return keys[clampedIndex].value.clone();\n }\n\n private static _GetRotationAtFrameIndex(keys: IAnimationKey[], frameIndex: number): Quaternion {\n if (keys.length === 0) {\n return Quaternion.Identity();\n }\n\n // Clamp frame index to valid range\n const clampedIndex = Math.max(0, Math.min(frameIndex, keys.length - 1));\n return keys[clampedIndex].value.clone();\n }\n\n private static _QuaternionToEuler(quaternion: Quaternion): Vector3 {\n // Convert quaternion to Euler angles in ZXY order for BVH\n const matrix = new Matrix();\n quaternion.toRotationMatrix(matrix);\n\n const m = matrix.m;\n let x, y, z;\n\n // ZXY rotation order extraction\n const sy = Math.sqrt(m[0] * m[0] + m[1] * m[1]);\n\n if (sy > Epsilon) {\n x = Math.atan2(m[6], m[10]);\n y = Math.atan2(-m[2], sy);\n z = Math.atan2(m[1], m[0]);\n } else {\n x = Math.atan2(-m[9], m[5]);\n y = Math.atan2(-m[2], sy);\n z = 0;\n }\n\n return new Vector3(Tools.ToDegrees(x), Tools.ToDegrees(y), Tools.ToDegrees(z));\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babylonjs/serializers",
3
- "version": "8.25.2",
3
+ "version": "8.26.1",
4
4
  "main": "index.js",
5
5
  "module": "index.js",
6
6
  "types": "index.d.ts",
@@ -18,10 +18,10 @@
18
18
  "postcompile": "build-tools -c add-js-to-es6"
19
19
  },
20
20
  "devDependencies": {
21
- "@babylonjs/core": "^8.25.2",
21
+ "@babylonjs/core": "^8.26.1",
22
22
  "@dev/build-tools": "^1.0.0",
23
23
  "@lts/serializers": "^1.0.0",
24
- "babylonjs-gltf2interface": "^8.25.2"
24
+ "babylonjs-gltf2interface": "^8.26.1"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "@babylonjs/core": "^8.0.0",