public static BoneKeyframe AssembleBones(StudioMdlWriter meshBuilder, BasePart rootPart) { Rbx2Source.Print("Building Skeleton..."); BoneKeyframe kf = new BoneKeyframe(); List <Bone> bones = kf.Bones; List <Node> nodes = meshBuilder.Nodes; Bone rootBone = new Bone(rootPart.Name, rootPart); rootBone.C0 = new CFrame(); rootBone.IsAvatarBone = true; bones.Add(rootBone); Node rootNode = rootBone.Node; rootNode.NodeIndex = 0; nodes.Add(rootNode); // Assemble the base rig. BoneAssemblePrep prep = new BoneAssemblePrep(ref bones, ref nodes); GenerateBones(prep, rootPart.GetChildrenOfClass <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 StudioMdlWriter AssembleModel(Folder characterAssets, AvatarScale scale, bool collisionModel = false) { StudioMdlWriter meshBuilder = new StudioMdlWriter(); // Build Character Folder import = RBXM.LoadFromAsset(R6AssemblyAsset); Folder assembly = import.FindFirstChild <Folder>("ASSEMBLY"); assembly.Parent = characterAssets; BasePart head = assembly.FindFirstChild <BasePart>("Head"); BasePart torso = assembly.FindFirstChild <BasePart>("Torso"); torso.CFrame = new CFrame(); foreach (Instance asset in characterAssets.GetChildren()) { if (asset.IsA("CharacterMesh") && !collisionModel) { CharacterMesh characterMesh = (CharacterMesh)asset; string limbName = LimbMatcher[characterMesh.BodyPart]; MeshPart limb = assembly.FindFirstChild <MeshPart>(limbName); if (limb != null) { limb.MeshId = "rbxassetid://" + characterMesh.MeshId; } } else if (asset.IsA("Accoutrement") && !collisionModel) { PrepareAccessory(asset, assembly); } else if (asset.IsA("DataModelMesh")) { OverwriteHead(asset, head); } } BoneKeyframe keyframe = AssembleBones(meshBuilder, torso); foreach (Bone bone in keyframe.Bones) { BuildAvatarGeometry(meshBuilder, bone); } return(meshBuilder); }
public StudioMdlWriter AssembleModel(Folder characterAssets, AvatarScale scale, bool collisionModel = false) { Contract.Requires(characterAssets != null && scale != null); StudioMdlWriter meshBuilder = new StudioMdlWriter(); // Build Character var import = R6AssemblyAsset.OpenAsModel(); Folder assembly = import.FindFirstChild <Folder>("ASSEMBLY"); assembly.Parent = characterAssets; BasePart head = assembly.FindFirstChild <BasePart>("Head"); BasePart torso = assembly.FindFirstChild <BasePart>("Torso"); torso.CFrame = new CFrame(); foreach (Instance asset in characterAssets.GetChildren()) { if (asset is CharacterMesh && !collisionModel) { var characterMesh = asset as CharacterMesh; string limbName = LimbMatcher[characterMesh.BodyPart]; var limb = assembly.FindFirstChild <MeshPart>(limbName); limb.MeshId = "rbxassetid://" + characterMesh.MeshId; } else if (asset is Accoutrement && !collisionModel) { PrepareAccessory(asset, assembly); } else if (asset is DataModelMesh) { OverwriteHead(asset as DataModelMesh, head); } } BoneKeyframe keyframe = AssembleBones(meshBuilder, torso); foreach (StudioBone bone in keyframe.Bones) { BuildAvatarGeometry(meshBuilder, bone); } return(meshBuilder); }
/// <summary> /// Reads in an XNB stream and converts it to a ModelInfo object /// </summary> /// <param name="input">The stream from which the data will be read</param> /// <param name="existingInstance">Not used</param> /// <returns>The unserialized ModelAnimationCollection object</returns> protected override AnimationInfoCollection Read(ContentReader input, AnimationInfoCollection existingInstance) { AnimationInfoCollection dict = new AnimationInfoCollection(); int numAnimations = input.ReadInt32(); // Read all the animations for (int i = 0; i < numAnimations; i++) { string animationName = input.ReadString(); int numBoneAnimations = input.ReadInt32(); List <BoneKeyframeCollection> animList = new List <BoneKeyframeCollection>(); // Read all the animation tracks for the current animation for (int j = 0; j < numBoneAnimations; j++) { string boneName = input.ReadString(); int numKeyFrames = input.ReadInt32(); List <BoneKeyframe> boneAnimationList = new List <BoneKeyframe>(); // Read all the keyframes for the current animation track for (int k = 0; k < numKeyFrames; k++) { BoneKeyframe frame = new BoneKeyframe( input.ReadMatrix(), input.ReadInt64()); boneAnimationList.Add(frame); } BoneKeyframeCollection boneAnimation = new BoneKeyframeCollection(boneName, boneAnimationList); animList.Add(boneAnimation); } AnimationInfo anim = new AnimationInfo(animationName, new AnimationChannelCollection(animList)); dict.Add(animationName, anim); } return(dict); }
public BoneKeyframe GetOrCreateBoneKeyFrame(string boneName, int frame) { Dictionary <int, BoneKeyframe> framesForBone; if (frame > Length) { Length = frame; } if (!BoneMotions.TryGetValue(boneName, out framesForBone)) { framesForBone = new Dictionary <int, BoneKeyframe>(); BoneMotions.Add(boneName, framesForBone); } BoneKeyframe boneKeyframe; if (!framesForBone.TryGetValue(frame, out boneKeyframe)) { boneKeyframe = new BoneKeyframe(); framesForBone.Add(frame, boneKeyframe); } return(boneKeyframe); }
public static string Assemble(KeyframeSequence sequence, List <Bone> rig) { StudioMdlWriter animWriter = new StudioMdlWriter(); List <Keyframe> keyframes = new List <Keyframe>(); Dictionary <string, Bone> boneLookup = new Dictionary <string, Bone>(); List <Node> nodes = animWriter.Nodes; foreach (Bone bone in rig) { Node node = bone.Node; if (node != null) { nodes.Add(node); string boneName = node.Name; if (!boneLookup.ContainsKey(boneName)) { boneLookup.Add(boneName, bone); } } } foreach (Keyframe kf in sequence.GetChildrenOfClass <Keyframe>()) { Pose rootPart = kf.FindFirstChild <Pose>("HumanoidRootPart"); if (rootPart != null) { // We don't need the rootpart for this. foreach (Pose subPose in rootPart.GetChildrenOfClass <Pose>()) { subPose.Parent = kf; } rootPart.Destroy(); } kf.Time /= sequence.TimeScale; keyframes.Add(kf); } keyframes.Sort(0, keyframes.Count, sorter); Keyframe lastKeyframe = keyframes[keyframes.Count - 1]; float fLength = lastKeyframe.Time; int frameCount = ToFrameRate(fLength); // Animations in source are kinda dumb, because theres no frame interpolation. // I have to account for every single CFrame for every single frame. Dictionary <int, Dictionary <string, Pose> > keyframeMap = new Dictionary <int, Dictionary <string, Pose> >(); for (int i = 0; i <= frameCount; i++) { keyframeMap[i] = new Dictionary <string, Pose>(); } foreach (Keyframe kf in keyframes) { int frame = ToFrameRate(kf.Time); Dictionary <string, Pose> poseMap = keyframeMap[frame]; List <Pose> poses = GatherPoses(kf); foreach (Pose pose in poses) { poseMap[pose.Name] = pose; } } List <BoneKeyframe> boneKeyframes = animWriter.Skeleton; Keyframe baseFrame = keyframes[0]; for (int i = 0; i < frameCount; i++) { BoneKeyframe frame = new BoneKeyframe(); frame.Time = i; if (sequence.AvatarType == AvatarType.R15) { frame.DeltaSequence = true; frame.BaseRig = rig; } List <Bone> bones = frame.Bones; foreach (Node node in nodes) { PosePair closestPoses = GetClosestPoses(keyframeMap, i, node.Name); float current = i; float min = closestPoses.Min.Frame; float max = closestPoses.Max.Frame; float alpha = (min == max ? 0 : (current - min) / (max - min)); Pose pose0 = closestPoses.Min.Pose; Pose pose1 = closestPoses.Max.Pose; float weight = EasingUtil.GetEasing(pose1.PoseEasingStyle, pose1.PoseEasingDirection, 1 - alpha); CFrame lastCFrame = pose0.CFrame; CFrame nextCFrame = pose1.CFrame; Bone baseBone = boneLookup[node.Name]; CFrame c0 = baseBone.C0; CFrame c1 = baseBone.C1; CFrame interp = lastCFrame.lerp(nextCFrame, weight); // some ugly manual fixes. // todo: make this unnecessary :( if (sequence.AvatarType == AvatarType.R6) { Vector3 pos = interp.p; CFrame rot = interp - pos; if (node.Name == "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")) { pos *= new Vector3(-1, 1, 1); } if (node.Name.Contains("Arm") || node.Name.Contains("Leg")) { pos = new Vector3(pos.z, pos.y, pos.x); } if (sequence.Name == "Climb" && node.Name.Contains("Leg")) // https://www.youtube.com/watch?v=vfJ7DqyDl9w { pos += new Vector3(-.1f, 0, 0); } interp = new CFrame(pos) * rot; } else if (sequence.AvatarType == AvatarType.R15) { if (node.Name.Contains("UpperArm")) { Vector3 pos = interp.p; CFrame rot = interp - pos; float[] ang = rot.toEulerAnglesXYZ(); if (sequence.Name == "Climb" || sequence.Name == "Swim") { rot = CFrame.Angles(ang[0], -ang[2], -ang[1]); } interp = new CFrame(pos) * rot; } } Bone bone = new Bone(node.Name, i, interp); bone.Node = node; bones.Add(bone); } boneKeyframes.Add(frame); } return(animWriter.BuildFile()); }
public StudioMdlWriter AssembleModel(Folder characterAssets, AvatarScale scale) { StudioMdlWriter meshBuilder = new StudioMdlWriter(); // Build Character Folder import = RBXM.LoadFromAsset(R15AssemblyAsset); Folder assembly = import.FindFirstChild <Folder>("ASSEMBLY"); assembly.Parent = characterAssets; Part head = assembly.FindFirstChild <Part>("Head"); Vector3 avatarScale = GetAvatarScale(scale); foreach (Instance asset in characterAssets.GetChildren()) { if (asset.IsA("Part")) { Part existing = assembly.FindFirstChild <Part>(asset.Name); if (existing != null) { existing.Destroy(); } asset.Parent = assembly; } else if (asset.IsA("Accoutrement")) { PrepareAccessory(asset, assembly); } else if (asset.IsA("DataModelMesh")) { OverwriteHead(asset, head); } } // Avatar Scaling foreach (Part part in assembly.GetChildrenOfClass <Part>()) { Limb limb = GetLimb(part); if (limb != Limb.Unknown) { part.Size *= avatarScale; foreach (Attachment attachment in part.GetChildrenOfClass <Attachment>()) { attachment.CFrame = CFrame.Scale(attachment.CFrame, avatarScale); } } } Part torso = assembly.FindFirstChild <Part>("LowerTorso"); torso.CFrame = new CFrame(); BoneKeyframe keyframe = AssembleBones(meshBuilder, torso); List <Bone> bones = keyframe.Bones; // Build File Data. Rbx2Source.Print("Building Geometry..."); Rbx2Source.IncrementStack(); foreach (Bone bone in bones) { BuildAvatarGeometry(meshBuilder, bone); } Rbx2Source.DecrementStack(); return(meshBuilder); }
public static string Assemble(KeyframeSequence sequence, List <Bone> rig) { StudioMdlWriter animWriter = new StudioMdlWriter(); List <Keyframe> keyframes = new List <Keyframe>(); var boneLookup = new Dictionary <string, Bone>(); var nodes = animWriter.Nodes; foreach (Bone 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.GetChildrenOfClass <Keyframe>()) { Pose rootPart = kf.FindFirstChild <Pose>("HumanoidRootPart"); if (rootPart != null) { // We don't need the rootpart for this. foreach (Pose subPose in rootPart.GetChildrenOfClass <Pose>()) { subPose.Parent = kf; } rootPart.Destroy(); } kf.Time /= sequence.TimeScale; 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++) { BoneKeyframe frame = new BoneKeyframe(i); List <Bone> bones = frame.Bones; if (sequence.AvatarType == AvatarType.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; Bone 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. if (sequence.AvatarType == AvatarType.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")) { // X-axis is inverted for the right arm/leg. pos *= new Vector3(-1, 1, 1); } if (node.Name.EndsWith("Arm") || node.Name.EndsWith("Leg")) { // 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 (sequence.AvatarType == AvatarType.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); } Bone bone = new Bone(node, interp); bones.Add(bone); } boneKeyframes.Add(frame); } return(animWriter.BuildFile()); }
public StudioMdlWriter AssembleModel(Folder characterAssets, AvatarScale scale, bool collisionModel = false) { Contract.Requires(characterAssets != null); StudioMdlWriter meshBuilder = new StudioMdlWriter(); // Build Character var import = R15AssemblyAsset.OpenAsModel(); Folder assembly = import.FindFirstChild <Folder>("ASSEMBLY"); BasePart head = assembly.FindFirstChild <BasePart>("Head"); assembly.Parent = characterAssets; foreach (Instance asset in characterAssets.GetChildren()) { if (asset is BasePart) { BasePart existing = assembly.FindFirstChild <BasePart>(asset.Name); if (existing != null) { existing.Destroy(); } asset.Parent = assembly; } else if (asset is Folder && asset.Name == "R15ArtistIntent") { foreach (BasePart child in asset.GetChildrenOfType <BasePart>()) { BasePart existing = assembly.FindFirstChild <BasePart>(child.Name); if (existing != null) { existing.Destroy(); } child.Parent = assembly; } } else if (asset is Accoutrement && !collisionModel) { PrepareAccessory(asset, assembly); } else if (asset is DataModelMesh) { OverwriteHead(asset as DataModelMesh, head); } } // Apply limb scaling var parts = assembly.GetChildrenOfType <BasePart>(); var attachMap = new Dictionary <string, Attachment>(); var avatarParts = parts.Where((part) => { var limb = GetLimb(part); return(limb.HasValue); }); var accessoryParts = parts.Except(avatarParts); foreach (BasePart avatarPart in avatarParts) { Vector3 limbScale = ComputeLimbScale(scale, avatarPart); foreach (Attachment att in avatarPart.GetChildrenOfType <Attachment>()) { attachMap[att.Name] = att; } ScalePart(avatarPart, limbScale); } // Apply accessory scaling foreach (BasePart handle in accessoryParts) { Attachment handleAtt = handle.FindFirstChildOfClass <Attachment>(); if (handleAtt != null) { string attName = handleAtt.Name; if (attachMap.ContainsKey(attName)) { Attachment avatarAtt = attachMap[attName]; BasePart avatarPart = avatarAtt.Parent as BasePart; Vector3 accessoryScale = ComputeAccessoryScale(scale, avatarPart, handle); ScalePart(handle, accessoryScale); } } } BasePart torso = assembly.FindFirstChild <BasePart>("LowerTorso"); torso.CFrame = new CFrame(); BoneKeyframe keyframe = AssembleBones(meshBuilder, torso); List <StudioBone> bones = keyframe.Bones; // Build File Data. Rbx2Source.Print("Building Geometry..."); Rbx2Source.IncrementStack(); foreach (StudioBone bone in bones) { BuildAvatarGeometry(meshBuilder, bone); } Rbx2Source.DecrementStack(); return(meshBuilder); }
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); }
public static StudioMdlWriter AssembleModel(Asset asset) { Folder content = RBXM.LoadFromAsset(asset); Rbx2Source.ScheduleTasks("GatherParts", "BuildMesh"); Rbx2Source.PrintHeader("GATHERING PARTS"); List <Part> parts = new List <Part>(); AddParts(parts, content); if (parts.Count == 0) { throw new Exception("No parts were found inside of this asset!"); } Part primaryPart = null; foreach (Part part in parts) { if (part.IsA("MeshPart") || part.Name == "Handle") { primaryPart = part; break; } } if (primaryPart == null) // k lol { primaryPart = parts[0]; } primaryPart.Name = asset.ProductInfo.Name; // Mark the primaryPart's location as the center. CFrame rootCoord = primaryPart.CFrame; foreach (Part 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 <Bone> bones = skeleton.Bones; List <Node> nodes = writer.Nodes; List <Triangle> triangles = writer.Triangles; Dictionary <string, Material> materials = writer.Materials; Dictionary <string, int> nameCounts = new Dictionary <string, int>(); int numAssembledParts = 0; foreach (Part 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.ToString(); part.Name = name; } else { nameCounts[name] = 0; } // Assemble the part. Material material = new Material(); Mesh geometry = Mesh.BakePart(part, material); if (geometry != null && geometry.FaceCount > 0) { string task = "BuildGeometry_" + name; Rbx2Source.ScheduleTasks(task); Rbx2Source.Print("Building Geometry for {0}", name); Bone bone = new Bone(name, primaryPart, part); bone.C0 = part.CFrame; bones.Add(bone); Node node = bone.Node; nodes.Add(node); materials.Add(name, material); for (int i = 0; i < geometry.FaceCount; i++) { Triangle tri = new Triangle(); tri.Node = node; tri.Mesh = geometry; tri.FaceIndex = i; tri.Material = name; triangles.Add(tri); } Rbx2Source.MarkTaskCompleted(task); numAssembledParts++; } } Rbx2Source.MarkTaskCompleted("BuildMesh"); return(writer); }