private int FindParent(List <Node> nodes) { BasePart part0 = StudioBone.Part0, part1 = StudioBone.Part1; if (part0 != part1) { Node parent = null; foreach (Node n in nodes) { StudioBone b = n.StudioBone; if (b != StudioBone && b.Part1 == part0) { parent = n; break; } } return(nodes.IndexOf(parent)); } return(-1); }
private static void GenerateBones(BoneAssemblePrep prep, Attachment[] queue) { if (queue.Length == 0) { return; } Instance bin = queue[0].Parent.Parent; foreach (Attachment a0 in queue) { List <Attachment> a1s = FindOtherAttachments(a0, bin); foreach (Attachment a1 in a1s) { if (a1 != null && !prep.Completed.Contains(a1)) { BasePart part0 = (BasePart)a0.Parent; BasePart part1 = (BasePart)a1.Parent; bool isRigAttachment = a0.Name.EndsWith("RigAttachment", StringComparison.InvariantCulture); if (isRigAttachment || prep.AllowNonRigs) { StudioBone bone = new StudioBone(part1.Name, part0, part1) { C0 = a0.CFrame, C1 = a1.CFrame, }; bone.IsAvatarBone = !prep.AllowNonRigs; prep.Bones.Add(bone); Node node = bone.Node; node.NodeIndex = prep.Bones.IndexOf(bone); prep.Nodes.Add(node); if (!prep.Completed.Contains(a0)) { prep.Completed.Add(a0); } prep.Completed.Add(a1); if (!prep.AllowNonRigs) { GenerateBones(prep, part1.GetChildrenOfType <Attachment>()); } } else // We'll deal with Accessory attachments afterwards. { prep.NonRigs.Add(a0); } } } } }
public static void BuildAvatarGeometry(StudioMdlWriter meshBuilder, StudioBone bone) { Contract.Requires(meshBuilder != null && bone != null); string task = "BuildGeometry_" + bone.Node.Name; Rbx2Source.ScheduleTasks(task); Node node = bone.Node; BasePart part = bone.Part1; bool isAvatarLimb = bone.IsAvatarBone; string matName = part.Name; if (isAvatarLimb) { BodyPart?limb = GetLimb(part); if (!limb.HasValue) { throw new ArgumentException("Provided StudioBone did not point to a limb correctly."); } matName = Rbx2Source.GetEnumName(limb.Value); } var material = new ValveMaterial() { UseAvatarMap = isAvatarLimb }; Rbx2Source.Print("Building Geometry for {0}", part.Name); Rbx2Source.IncrementStack(); Mesh geometry = Mesh.BakePart(part, material); meshBuilder.Materials[matName] = material; for (int i = 0; i < geometry.NumFaces; i++) { Triangle tri = new Triangle() { Node = node, FaceIndex = i, Mesh = geometry, Material = matName }; meshBuilder.Triangles.Add(tri); } Rbx2Source.DecrementStack(); Rbx2Source.MarkTaskCompleted(task); }
public void WriteStudioMdl(StringWriter fileBuffer, List <BoneKeyframe> skeleton) { Contract.Requires(fileBuffer != null && skeleton != null); fileBuffer.WriteLine("time " + Time); foreach (StudioBone bone in Bones) { int boneIndex = Bones.IndexOf(bone); fileBuffer.Write(boneIndex + " "); int parentIndex = bone.Node.ParentIndex; CFrame boneCFrame = bone.C0; if (DeltaSequence) { StudioBone refBone = BaseRig[boneIndex]; boneCFrame = refBone.C0 * boneCFrame; Node refNode = refBone.Node; int refParentIndex = refNode.ParentIndex; if (refParentIndex >= 0) { StudioBone refParent = BaseRig[refParentIndex]; boneCFrame = refParent.C1.Inverse() * boneCFrame; } } else if (parentIndex >= 0) { StudioBone parentBone = Bones[parentIndex]; boneCFrame *= parentBone.C1.Inverse(); } Vector3 pos = boneCFrame.Position * Rbx2Source.MODEL_SCALE; Vector3 rot = new Vector3(boneCFrame.ToEulerAnglesXYZ()); fileBuffer.Write(Format.FormatFloats ( pos.X, pos.Y, pos.Z, rot.X, rot.Y, rot.Z )); fileBuffer.WriteLine(); } }
public static BoneKeyframe AssembleBones(StudioMdlWriter meshBuilder, BasePart rootPart) { Contract.Requires(meshBuilder != null); Contract.Requires(rootPart != null); Rbx2Source.Print("Building Skeleton..."); BoneKeyframe kf = new BoneKeyframe(); List <StudioBone> bones = kf.Bones; List <Node> nodes = meshBuilder.Nodes; StudioBone rootBone = new StudioBone(rootPart.Name, rootPart) { C0 = new CFrame(), IsAvatarBone = true }; Node rootNode = rootBone.Node; rootNode.NodeIndex = 0; bones.Add(rootBone); nodes.Add(rootNode); // Assemble the base rig. BoneAssemblePrep prep = new BoneAssemblePrep(ref bones, ref nodes); GenerateBones(prep, rootPart.GetChildrenOfType <Attachment>()); // Assemble the accessories. prep.AllowNonRigs = true; GenerateBones(prep, prep.NonRigs.ToArray()); // Apply the rig cframe data. ApplyBoneCFrames(rootPart); meshBuilder.Skeleton.Add(kf); return(kf); }
public static string Assemble(KeyframeSequence sequence, List <StudioBone> rig) { Contract.Requires(sequence != null && rig != null); StudioMdlWriter animWriter = new StudioMdlWriter(); List <Keyframe> keyframes = new List <Keyframe>(); var boneLookup = new Dictionary <string, StudioBone>(); var nodes = animWriter.Nodes; foreach (StudioBone bone in rig) { Node node = bone.Node; if (node != null) { string boneName = node.Name; if (!boneLookup.ContainsKey(boneName)) { boneLookup.Add(boneName, bone); } nodes.Add(node); } } foreach (Keyframe kf in sequence.GetChildrenOfType <Keyframe>()) { Pose rootPart = kf.FindFirstChild <Pose>("HumanoidRootPart"); if (rootPart != null) { // We don't need the rootpart for this. foreach (Pose subPose in rootPart.GetChildrenOfType <Pose>()) { subPose.Parent = kf; } rootPart.Destroy(); } keyframes.Add(kf); } keyframes.Sort(0, keyframes.Count, sorter); Keyframe lastKeyframe = keyframes[keyframes.Count - 1]; float fLength = lastKeyframe.Time; int frameCount = ToFrameRate(fLength); // As far as I can tell, models in Source require you to store poses for every // single frame, so I need to fill in the gaps with interpolated pose CFrames. var keyframeMap = new Dictionary <int, Dictionary <string, Pose> >(); foreach (Keyframe kf in keyframes) { int frame = ToFrameRate(kf.Time); var poses = GatherPoses(kf); var poseMap = poses.ToDictionary(pose => pose.Name); keyframeMap[frame] = poseMap; } // Make sure there are no holes in the data. for (int i = 0; i < frameCount; i++) { if (!keyframeMap.ContainsKey(i)) { var emptyState = new Dictionary <string, Pose>(); keyframeMap.Add(i, emptyState); } } List <BoneKeyframe> boneKeyframes = animWriter.Skeleton; for (int i = 0; i < frameCount; i++) { var frame = new BoneKeyframe(i); List <StudioBone> bones = frame.Bones; var avatarTypeId = sequence.FindFirstChild <StringValue>("AvatarType"); if (avatarTypeId.Value == "R15") { frame.BaseRig = rig; frame.DeltaSequence = true; } foreach (Node node in nodes) { PosePair closestPoses = GetClosestPoses(keyframeMap, i, node.Name); float min = closestPoses.Min.Frame; float max = closestPoses.Max.Frame; float alpha = (min == max ? 0 : (i - min) / (max - min)); Pose pose0 = closestPoses.Min.Pose; Pose pose1 = closestPoses.Max.Pose; CFrame lastCFrame = pose0.CFrame; CFrame nextCFrame = pose1.CFrame; StudioBone baseBone = boneLookup[node.Name]; CFrame interp = lastCFrame.Lerp(nextCFrame, alpha); // Make some patches to the interpolation offsets. Unfortunately I can't // identify any single fix that I can apply to each joint, so I have to get crafty. // At some point in the future, I want to find a more practical solution for this problem, // but it is extremely difficult to isolate if any single solution exists. var invariant = StringComparison.InvariantCulture; if (avatarTypeId.Value == "R6") { Vector3 pos = interp.Position; CFrame rot = interp - pos; if (node.Name == "Torso") { // Flip the YZ axis of the Torso. float[] ang = interp.ToEulerAnglesXYZ(); rot = CFrame.Angles(ang[0], ang[2], ang[1]); pos = new Vector3(pos.X, pos.Z, pos.Y); } else if (node.Name.StartsWith("Right", invariant)) { // X-axis is inverted for the right arm/leg. pos *= new Vector3(-1, 1, 1); } if (node.Name.EndsWith("Arm", invariant) || node.Name.EndsWith("Leg", invariant)) { // Rotate position offset of the arms & legs 90* counter-clockwise. pos = new Vector3(-pos.Z, pos.Y, pos.X); } if (node.Name != "Head") { rot = rot.Inverse(); } interp = new CFrame(pos) * rot; } else if (avatarTypeId.Value == "R15") { float[] ang = interp.ToEulerAnglesXYZ(); // Cancel out the rotations interp *= CFrame.Angles(-ang[0], -ang[1], -ang[2]); // Patch the Y-axis PatchAngles(ref interp, 1, ang); // Patch the Z-axis PatchAngles(ref interp, 2, ang); // Patch the X-axis PatchAngles(ref interp, 0, ang); } StudioBone bone = new StudioBone(node, interp); bones.Add(bone); } boneKeyframes.Add(frame); } string result = animWriter.BuildFile(); animWriter.Dispose(); return(result); }
public static StudioMdlWriter AssembleModel(Asset asset) { Contract.Requires(asset != null); var content = asset.OpenAsModel(); Rbx2Source.ScheduleTasks("GatherParts", "BuildMesh"); List <BasePart> parts = new List <BasePart>(); AddParts(parts, content); if (parts.Count == 0) { throw new Exception("No parts were found inside of this asset!"); } BasePart primaryPart = null; foreach (BasePart part in parts) { if (part is MeshPart || part.Name == "Handle") { primaryPart = part; break; } } if (primaryPart == null) // k lol { primaryPart = parts[0]; } primaryPart.Name = asset.ProductInfo.WindowsSafeName.Trim(); // Mark the primaryPart's location as the center. CFrame rootCoord = primaryPart.CFrame; foreach (BasePart part in parts) { part.CFrame = rootCoord.ToObjectSpace(part.CFrame); } Rbx2Source.MarkTaskCompleted("GatherParts"); Rbx2Source.PrintHeader("BUILDING MESH"); StudioMdlWriter writer = new StudioMdlWriter(); BoneKeyframe skeleton = new BoneKeyframe(); writer.Skeleton.Add(skeleton); List <StudioBone> bones = skeleton.Bones; List <Node> nodes = writer.Nodes; List <Triangle> triangles = writer.Triangles; int numAssembledParts = 0; var materials = writer.Materials; var nameCounts = new Dictionary <string, int>(); foreach (BasePart part in parts) { // Make sure this part has a unique name. string name = part.Name; if (nameCounts.ContainsKey(name)) { int count = ++nameCounts[name]; name += count.ToInvariantString(); part.Name = name; } else { nameCounts[name] = 0; } // Assemble the part. var material = new ValveMaterial(); Mesh geometry = Mesh.BakePart(part, material); if (geometry != null && geometry.NumFaces > 0) { string task = "BuildGeometry_" + name; Rbx2Source.ScheduleTasks(task); Rbx2Source.Print("Building Geometry for {0}", name); var bone = new StudioBone(name, primaryPart, part) { C0 = part.CFrame }; bones.Add(bone); Node node = bone.Node; nodes.Add(node); int faceStride; materials.Add(name, material); if (geometry.HasLODs) { faceStride = geometry.LODs[1]; } else { faceStride = geometry.NumFaces; } for (int i = 0; i < faceStride; i++) { Triangle tri = new Triangle() { Node = node, Mesh = geometry, FaceIndex = i, Material = name, }; triangles.Add(tri); } Rbx2Source.MarkTaskCompleted(task); numAssembledParts++; } } Rbx2Source.MarkTaskCompleted("BuildMesh"); return(writer); }