Edit: Mapping BVH Data.
BVH data points are mapped to graph vertices, which are then manually assigned to corresponding bones.
Using the BVH archive from https://accad.osu.edu/sites/accad.osu.edu/files/Female1_bvh.zip
Preparing BVH data points
Borrowing Sjoerd C. de Vries's parsing code from How to build a bvh player:
filename = "Female1_B01_StandToWalk.bvh";
bvh = Import[NotebookDirectory[] <> filename, "String"];
bvhHierarchy =
StringReplace[
bvh, ___ ~~ "HIERARCHY" ~~ start__ ~~ "MOTION" ~~ ___ :> start];
bvhMotion = StringReplace[bvh, ___ ~~ "MOTION" ~~ end___ :> end];
tokenize[code_String] :=
StringSplit[code, (" " | "\n" | "\r" | "\t") ..]
{frames, frameTime, data} =
tokenize[
bvhMotion] /. {___, "Frames:", frs_, "Frame", "Time:", ft_,
data__} :> {ToExpression@frs, ToExpression@ft,
Partition[N[#, 6] & /@ {data}, Length[{data}]/ToExpression[frs]]};
ClearAll[channelPattern, offsetPattern, bracketPattern]
bracketPattern = PatternSequence["{", Except["{" | "}"] .., "}"];
channelPattern =
PatternSequence["CHANNELS", n_, channels__] /;
Length[{channels}] == ToExpression[n];
offsetPattern = PatternSequence["OFFSET", x_, y_, z_];
ClearAll[parse]
parse[left___, cp : channelPattern, right___] :=
expr[parse[left], offset @@ ({cp}[[3 ;; 5]]),
channel @@ ({cp}[[6 ;;]]), parse[right]] /; Length[{cp}] == 8
parse[left___, cp : channelPattern, right___] :=
expr[parse[left], channel @@ ({cp}[[3 ;;]]), parse[right]] /;
Length[{cp}] == 5
parse[left___, op : offsetPattern, right___] :=
expr[parse[left], offset @@ ({op}[[2 ;;]]), parse[right]];
parse[{tokens___}] := parse[tokens];
parse[tokens___] := expr[tokens];
ClearAll[parseBrackets, a]
parseBrackets[left___, h1_, h2_, bp : bracketPattern, right___] :=
parseBrackets[left, h1 @@ ({bp}[[2 ;; -2]]), right];
makeSlots[tokens_] :=
Module[{i = 1},
Which[MatchQ[#,
Alternatives["Xposition", "Yposition", "Zposition"]],
slot[i++], # === "Xrotation", xr[slot[i++]], # === "Yrotation",
yr[slot[i++]], # === "Zrotation", zr[slot[i++]],
Head[#] == String && StringMatchQ[#, NumberString],
ToExpression[#], True, #] & /@ tokens]
xRot[x_] := RotationMatrix[x Degree, {1, 0, 0}];
yRot[y_] := RotationMatrix[y Degree, {0, 1, 0}];
zRot[z_] := RotationMatrix[z Degree, {0, 0, 1}];
channelToRotation[c_channel] :=
Dot @@ (c /. {xr -> xRot, yr -> yRot, zr -> zRot})
ClearAll[ROOT, JOINT]
ROOT[os_List, d_List, r_List, rest___] := ROOT[os + d, r, rest]
ROOT[os_List, r_List, p : PatternSequence[__JOINT, ___END]] :=
ROOT[os, r, #] & /@ {p} /; Length[{p}] > 1
ROOT[os_List, r_List, p_JOINT] := {Line[{os, os + r . p[[1]]}],
ROOT[os + r . p[[1]], r . p[[2]], Sequence @@ p[[3 ;;]]]}
ROOT[os_List, r_List, p_END] := Line[{os, os + r . p[[1]]}]
bodyFunction =
Function[
parseBrackets@
parse@makeSlots@tokenize@bvhHierarchy /. {expr -> Sequence,
slot -> Slot} // Evaluate];
Aligning BHV data points with the skeleton
ClearAll[align] (* manually adjusted *)
align[point_] := Threaded[{300, 200, -1100}] +
ScalingMatrix[{-10, 10, 10}] .
RotationMatrix[90 Degree, {1, 0, 0}] . # &@point
ClearAll[bvhPoints]
bvhPoints[i_] := DeleteDuplicates@Flatten[List @@@ Flatten[
bodyFunction @@ (ToExpression /@ data[[i]]) /.
{offset -> List, c_channel :> channelToRotation[c]} /.
{"ROOT" -> ROOT, "JOINT" -> JOINT, "End" -> END} /.
{parseBrackets[a : ___] :> a}], 2]
ClearAll[center]
center[1] = Identity;
center[i_] := FindGeometricTransform[{bvhPoints[1][[1]]}, {bvhPoints[i][[1]]} ][[2]]
ClearAll[graphPoints, scaledPoints]
graphPoints[i_] := <|MapIndexed[Rule[#1, First@#2] &]@bvhPoints[i]|>
scaledPoints[i_] := <|MapIndexed[Rule[First@#2, #1] &]@(align@*center[i] /@ bvhPoints[i])|>
ClearAll[graph, f]
graph[i_] := Flatten[f @@@ Flatten[
bodyFunction @@ (ToExpression /@ data[[i]]) /.
{offset -> List, c_channel :> channelToRotation[c]} /.
{"ROOT" -> ROOT, "JOINT" -> JOINT, "End" -> END} /.
{parseBrackets[a : ___] :> a}], 2] /.
f[List[x_, y_]] :> UndirectedEdge[graphPoints[i][x], graphPoints[i][y]]
Show[Graph3D[graph[1],
VertexCoordinates -> (align@*center[1] /@ bvhPoints[1])
, VertexShapeFunction -> (Sphere[#1, 1] &)
, VertexLabels -> Automatic, EdgeStyle -> Blue]
, AnatomyPlot3D[ {Opacity[.3],
Entity["AnatomicalStructure", "SetOfBones"]}]
, Axes -> True, ViewPoint -> #] & /@ {Front, Left}
Transforming functions
ClearAll[component, transform]
component[from_, to_, iteration_] := component[from, to, iteration] =
{{scaledPoints[1][from], scaledPoints[1][to]}
, {scaledPoints[iteration][from], scaledPoints[iteration][to]}}
transform[{from_, to_}][structure_] :=
Module[{fromVector, toVector, rotationAxis, rotationAngle,
rotationMatrix, translationVector, translated, rotated},
fromVector = Subtract @@ from;
toVector = Subtract @@ to;
rotationAxis = Cross[fromVector, toVector];
rotationAngle = VectorAngle[fromVector, toVector];
rotationMatrix = RotationMatrix[rotationAngle, rotationAxis];
translationVector = First[to] - rotationMatrix . First[from];
rotated = GeometricTransformation[structure, rotationMatrix];
translated = TransformedRegion[rotated, TranslationTransform[translationVector]]
]
Mapping & frames generation
(* long runner ~5' on my machine - 16 Cores *)
frames = ParallelTable[
Graphics3D[
{Directive[EdgeForm[]
, Lighting -> {{"Ambient", RGBColor[0.35, 0.35, 0.35]}
, {"Directional", RGBColor[0.2, 0.2, 0.2], ImageScaled[{2, 0, 2}]}
, {"Directional", RGBColor[0.2, 0.2, 0.2], ImageScaled[{2, 2, 2}]}
, {"Directional", RGBColor[0.2, 0.2, 0.2], ImageScaled[{0, 2, 2}]}}
, Specularity[GrayLevel[0.5], 1.5], RGBColor[0.88, 0.847, 0.748]]
, transform[component[23, 24, n]][
Entity["AnatomicalStructure", "LeftFemur"]["MeshRegion"]]
, transform[component[24, 25, n]][
Entity["AnatomicalStructure", "LeftTibia"]["MeshRegion"]]
, transform[component[24, 25, n]][
Entity["AnatomicalStructure", "LeftFibula"]["MeshRegion"]]
, transform[component[25, 27, n]][
Entity["AnatomicalStructure", "SkeletonOfLeftFoot"][
"MeshRegion"]]
, transform[component[18, 19, n]][
Entity["AnatomicalStructure", "RightFemur"]["MeshRegion"]]
, transform[component[19, 20, n]][
Entity["AnatomicalStructure", "RightTibia"]["MeshRegion"]]
, transform[component[19, 20, n]][
Entity["AnatomicalStructure", "RightFibula"]["MeshRegion"]]
, transform[component[20, 22, n]][
Entity["AnatomicalStructure", "SkeletonOfRightFoot"][
"MeshRegion"]]
, transform[component[14, 15, n]][
Entity["AnatomicalStructure", "LeftHumerus"]["MeshRegion"]]
, transform[component[15, 16, n]][
Entity["AnatomicalStructure", "LeftRadius"]["MeshRegion"]]
, transform[component[15, 16, n]][
Entity["AnatomicalStructure", "LeftUlna"]["MeshRegion"]]
, transform[component[16, 17, n]][
Entity["AnatomicalStructure", "SkeletonOfLeftHand"][
"MeshRegion"]]
, transform[component[9, 10, n]][
Entity["AnatomicalStructure", "RightHumerus"]["MeshRegion"] ]
, transform[component[10, 11, n]][
Entity["AnatomicalStructure", "RightRadius"]["MeshRegion"]]
, transform[component[10, 11, n]][
Entity["AnatomicalStructure", "RightUlna"]["MeshRegion"]]
, transform[component[11, 12, n]][
Entity["AnatomicalStructure", "SkeletonOfRightHand"][
"MeshRegion"]]
, transform[component[18, 23, n]][
Entity["AnatomicalStructure", "HipBone"] ["MeshRegion"]]
, transform[component[18, 23, n]][
Entity["AnatomicalStructure", "Sacrum"] ["MeshRegion"]]
, transform[component[3, 5, n]][
Entity["AnatomicalStructure", "Vertebra"]["MeshRegion"]]
, transform[component[13, 14, n]][
Entity["AnatomicalStructure", "LeftScapula"]["MeshRegion"]]
, transform[component[8, 9, n]][
Entity["AnatomicalStructure", "RightScapula"]["MeshRegion"] ]
, transform[component[13, 14, n]][
Entity["AnatomicalStructure", "LeftClavicle"]["MeshRegion"] ]
, transform[component[8, 9, n]][
Entity["AnatomicalStructure", "RightClavicle"]["MeshRegion"] ]
, transform[component[3, 5, n]][
Entity["AnatomicalStructure", "RibCage"]["MeshRegion"]]
, transform[component[6, 7, n]][
Entity["AnatomicalStructure", "Skull"]["MeshRegion"]]
}
, SphericalRegion -> True
, Boxed -> False
, ViewPoint -> {0.99, -1.61, -0.02}]
, {n, 2, Length@data -1, 1}];
Export["walking.gif", frames, "GIF"]
Original Post:
An attempt, perhaps as a starting point for something more sophisticated.
rightFoot =
Intersection[
EntityList @EntityClass["AnatomicalStructure", "Bones"] ,
Entity["AnatomicalStructure", "RightFoot"]["ConstitutionalParts"]];
leftFoot =
Intersection[
EntityList @EntityClass["AnatomicalStructure", "Bones"] ,
Entity["AnatomicalStructure", "LeftFoot"] ["ConstitutionalParts"]];
rightHand =
Intersection[
EntityList @EntityClass["AnatomicalStructure", "Bones"] ,
Entity["AnatomicalStructure", "RightHand"]["ConstitutionalParts"]];
leftHand =
Intersection[
EntityList @EntityClass["AnatomicalStructure", "Bones"] ,
Entity["AnatomicalStructure", "LeftHand"]["ConstitutionalParts"]];
{x, y, z } = {0, -96, 845};
Manipulate[
AnatomyPlot3D[
{Select[EntityList@EntityClass["AnatomicalStructure", "Bones"]
, FreeQ[{
Entity["AnatomicalStructure", "LeftFemur"]
, Entity["AnatomicalStructure", "LeftTibia"]
, Entity["AnatomicalStructure", "LeftFibula"]
, Entity["AnatomicalStructure", "LeftPatella"]
, Entity["AnatomicalStructure", "RightFemur"]
, Entity["AnatomicalStructure", "RightTibia"]
, Entity["AnatomicalStructure", "RightFibula"]
, Entity["AnatomicalStructure", "RightPatella"]
, Splice@rightFoot
, Splice@leftFoot
, Entity["AnatomicalStructure", "LeftHumerus"]
, Entity["AnatomicalStructure", "LeftUlna"]
, Entity["AnatomicalStructure", "LeftRadius"]
, Entity["AnatomicalStructure", "RightHumerus"]
, Entity["AnatomicalStructure", "RightUlna"]
, Entity["AnatomicalStructure", "RightRadius"]
, Splice@rightHand
, Splice@leftHand
, Entity["AnatomicalStructure", "Coccyx"]}, #] &]
, Rotate[{
Entity["AnatomicalStructure", "RightHumerus"]
, Entity["AnatomicalStructure", "RightUlna"]
, Entity["AnatomicalStructure", "RightRadius"]
, Splice@rightHand}
, Dynamic[20*Sin[\[Pi]*(t/40 + 0.5)] Degree], {1, 0, 0}, {x, y, z + 455}]
, Rotate[{
Entity["AnatomicalStructure", "LeftHumerus"]
, Entity["AnatomicalStructure", "LeftUlna"]
, Entity["AnatomicalStructure", "LeftRadius"]
, Splice@leftHand}
, Dynamic[-20*Sin[\[Pi]*(t/40 + 0.5)] Degree], {1, 0, 0}, {x, y, z + 455}]
, Rotate[{
Entity["AnatomicalStructure", "RightFemur"]
, Entity["AnatomicalStructure", "RightTibia"]
, Entity["AnatomicalStructure", "RightFibula"]
, Entity["AnatomicalStructure", "RightPatella"]
, Splice@rightFoot}
, Dynamic[-20*Sin[\[Pi]*(t/40 + 0.5)] Degree], {1, 0, 0}, {x, y, z}]
, Rotate[{
Entity["AnatomicalStructure", "LeftFemur"]
, Entity["AnatomicalStructure", "LeftTibia"]
, Entity["AnatomicalStructure", "LeftFibula"]
, Entity["AnatomicalStructure", "LeftPatella"]
, Splice@leftFoot}
, Dynamic[20*Sin[\[Pi]*(t/40 + 0.5)] Degree], {1, 0, 0}, {x, y, z}]
}
, SphericalRegion -> True
, ViewPoint -> {0.99, -1.61, -0.02}
, PlotRangePadding -> 20], {t, -40, 40, 2}
, SynchronousUpdating -> False]