FileIn (RsConfigGetWildWestDir() + "script/3dsmax/_config_files/wildwest_header.ms") FileIn "SkinnedObject_BoneHierarchy.ms" FileIn "SkinModifier.ms" /*--------------------------------------------------- Takes an animated geometry object and creates a SkinnedObject which mirrors it and the hierarchy beneath it. */--------------------------------------------------- fn SkinnedObjectFromAnimatedGeometryHierarchy RootObj = ( --Create a new SkinnedObject from the passed animated geometry object. ReturnSkinnedObject = SkinnedObject() ReturnSkinnedObject.CreateFromAnimatedGeometry RootObj --Then go through each of the children and recursively call this FN to create a SkinnedObject for each merging them into our root SkinnedObject as we go. for ChildObj in RootObj.Children do ( if( SuperClassOf ChildObj == GeometryClass) then ( ChildSkinnedObject = SkinnedObjectFromAnimatedGeometryHierarchy ChildObj ReturnSkinnedObject.MergeSkinnedObject ChildSkinnedObject ) ) return ReturnSkinnedObject ) /*--------------------------------------------------- Takes a skinned geometry object and creates a SkinnedObject which mirrors it */--------------------------------------------------- fn SkinnedObjectFromSkinnedGeometry Obj = ( --Create a new SkinnedObject from the passed skinned geometry object. ReturnSkinnedObject = SkinnedObject() ReturnSkinnedObject.CreateFromSkinnedGeometry Obj return ReturnSkinnedObject ) /*--------------------------------------------------- Represents a object which has skinning and an associated bone hierarchy. Provides various utiliy functions */--------------------------------------------------- struct SkinnedObject ( Name = "None", GUID = (DotNetClass "System.GUID").NewGuid(), MeshNode, MeshCache = #(), BoneHierarchy, /*--------------------------------------------------- Sets the name of the SkinnedObject and the assocaited MeshNode */--------------------------------------------------- fn SetName InputName = ( This.Name = InputName This.MeshNode.Name = InputName ), /*--------------------------------------------------- Sets up this SkinnedObject from an geometry object which has animation. The bone hierarchy will have a single bone which will be weighted to the whole piece of geometry. */--------------------------------------------------- fn CreateFromAnimatedGeometry InputGeometryObject = ( --Destroy what we currently have to start again. Just incase this instance of a SkinnedObject has already been intialised and used. This.Destroy() --Create a bone hierarchy from the passed InputGeometryObject. This FN also returns a mesh copy which the bone hierarchy is mapped to. This.MeshNode = This.BoneHierarchy.CreateFromAnimatedGeometry( InputGeometryObject ) ), /*--------------------------------------------------- Sets up this SkinnedObject from an input geometry object which has skinning applied. The bone hierarchy will mirror the set of bones in the SkinModifier which should be at the top of the stack. */--------------------------------------------------- fn CreateFromSkinnedGeometry InputSkinnedGeometryObject = ( --Destroy what we currently have to start again. Just incase this instance of a SkinnedObject has already been intialised and used. This.Destroy() --Create a bone hierarchy from the passed InputGeometryObject. This FN also returns a mesh copy which the bone hierarchy is mapped to. This.MeshNode = This.BoneHierarchy.CreateFromSkinnedGeometry( InputSkinnedGeometryObject ) ), /*--------------------------------------------------- Sets up this SkinnedObject from an input geometry object which has skinning applied. The bone hierarchy will mirror the set of bones in the SkinModifier which should be at the top of the stack. */--------------------------------------------------- fn CreateFromSingleBone InputBone InputMeshNode = ( --Destroy what we currently have to start again. Just incase this instance of a SkinnedObject has already been intialised and used. This.Destroy() --Copy this mesh object. Create a bit array for the inversion of the verts we need. Remove them so we are left with a mesh containing only verts related to the current bone we are working with. --Note that we can't do a detach operation because this could potentionally reorder the verts so our weights would no longer corrolate correctly ExtractedMesh = RSMesh_Snapshot InputMeshNode RemoveVertIDBitArray = #{1..ExtractedMesh.NumVerts} - (InputBone.GetVertIDArray() as BitArray) Meshop.DeleteVerts ExtractedMesh RemoveVertIDBitArray This.MeshNode = ExtractedMesh --Copy bone into the hierarchy and condense weights so they will be correct CopiedBone = This.BoneHierarchy.CopyBone InputBone 0 CopiedBone.CondenseWeights() ), /*--------------------------------------------------- Sets the material for the SkinnedObject */--------------------------------------------------- fn SetMaterial InputMaterial = ( if( ClassOf InputMaterial != MultiMaterial ) then ( InputMaterial = MaterialToMultiMaterial InputMaterial ) if( MeshNode.Material == Undefined ) then ( MeshNode.Material = InputMaterial ) else ( RSMesh_SnapshotMesh_ApplyMultiMaterial MeshNode InputMaterial ) ), /*--------------------------------------------------- Takes all the meshes in the This.MeshCache and merges them into the This.MeshNode using a chunking algorithm. The reason we do this is because max is very slow at attaching lots of meshes together so if we merge lots of skinned objects together like in the Stated Animation tool it will take alot of time. This way is much (to a very large factor) quicker. */--------------------------------------------------- fn CollapseMeshCache = ( SuspendEditing() --Ensure that all the meshes have the same material before we start attach operation for SubMesh in MeshCache do ( RSMesh_ApplyMultiMaterial SubMesh MeshNode.Material ) RSMesh_AttachArray MeshNode MeshCache This.ClearMeshCache() ResumeEditing() ), /*--------------------------------------------------- Clears the MeshCache */--------------------------------------------------- fn ClearMeshCache = ( for SubMesh in MeshCache do ( if IsValidNode SubMesh then Delete SubMesh ) MeshCache = #() ), /*--------------------------------------------------- Gets the number of verts contained in this SkinnedObject. Includes meshes in the MeshCache which may not have been merged into the main MeshNode. */--------------------------------------------------- fn GetVertCount = ( VertCount = GetNumVerts MeshNode for SubMesh in MeshCache do ( VertCount += GetNumVerts SubMesh ) return VertCount ), /*--------------------------------------------------- Gets the number of faces contained in this SkinnedObject. Includes meshes in the MeshCache which may not have been merged into the main MeshNode. */--------------------------------------------------- fn GetFaceCount = ( FaceCount = GetNumFaces MeshNode for SubMesh in MeshCache do ( FaceCount += GetNumFaces SubMesh ) return FaceCount ), /*--------------------------------------------------- Merges the passed SkinnedObject instance into this SkinnedObject instance. Both SkinnedObjects must have the same Material applied. Deletes the InputSkinnedObject after completion of the operation. */--------------------------------------------------- fn MergeSkinnedObject InputSkinnedObject = ( VertIDOffset = GetVertCount() --Go through each of the bones of the InputSkinnedObject, copy it and add into this SkinnedObject. --Offsetting each of the vertices with the vert ID offset for SubBone in InputSkinnedObject.BoneHierarchy.BoneArray do ( This.BoneHierarchy.CopyBone SubBone VertIDOffset ) --Get a mesh clone of the InputSkinnedObject and add it to our MeshCache InputMeshCopy = RSMesh_Snapshot InputSkinnedObject.MeshNode Append MeshCache InputMeshCopy --Delete InputSkinnedObject now we have successfully merged it InputSkinnedObject.Destroy() ), /*--------------------------------------------------------------- Skins the SkinnedObject based on all the information contained within. Should only be called once all SkinnedObject operations have been completed for this session. */--------------------------------------------------------------- fn SkinObject = ( --First we must collapse any meshes in the MeshCache into the main MeshNode ready for skinning CollapseMeshCache() --Name the mesh appropriately This.MeshNode.Name = This.Name --This.MeshNode.WireColor = Black --Create, add and setup skin modifier for edit NewSkinModifier = SkinModifier() NewSkinModifier.SetupSkinModifier This.MeshNode NewSkinModifier.StartEdit() --UnNormalize all the verts so we can push whatever values we want onto them. If we don't do this none of our values will hold. --This also creates and deletes a bone for us. Again important since the skin modifier won't seem to weight untill a bone --has been add and subsequently been removed. Fun. NewSkinModifier.UnNormalizeVerts True --This is a dummy bone because we can't seem to reliably weight the first bone. We delete later. DummyBone = Point name:(This.Name+"_TEMP_") NewSkinModifier.AddBone DummyBone --Setup our root bone. RootBone = Point name:(This.Name+"_Root") RootBone.Transform = This.MeshNode.Transform NewSkinModifier.AddBone RootBone BonePrefix = This.Name + "_Bone_" Format "Adding bones\n" --First we add all the bones. Doing this is quicker than adding and weighting a bone at a time for SubBone in This.BoneHierarchy.BoneArray do ( --Parent the bone to our root bone and name it SubBone.Node.Parent = RootBone SubBone.Node.Name = UniqueName BonePrefix NumDigits:3 --Add our bone NewSkinModifier.AddBone SubBone.Node ) --Then we push the new vertex weight values onto it BoneIndex = 3 for SubBone in This.BoneHierarchy.BoneArray do ( StatusString = "Setting bone weights for bone: " + (BoneIndex - 2) as String DisplayTempPrompt StatusString 1000 --Push the new values to each vert for VertexWeight in SubBone.VertexWeightTable do ( NewSkinModifier.SetVertexWeight VertexWeight.VertID BoneIndex VertexWeight.Weight ) GC() BoneIndex += 1 ) --Delete the temp root bone and end editing. By deleting the temp bone max automatically removes it from the Skin Delete DummyBone NewSkinModifier.EndEdit() ), /*--------------------------------------------------- Returns an array of the bones which are isolated */--------------------------------------------------- fn GetIsolatedBoneArray = ( return (for SubBone in This.BoneHierarchy.BoneArray where This.IsIsolatedBone SubBone collect SubBone) ), /*--------------------------------------------------- Returns True/False if the bone is isolated. */--------------------------------------------------- fn IsIsolatedBone InputBone = ( --Collapse mesh cache before continuing CollapseMeshCache() --Get all the vert IDs that this bone affects VertIDArray = InputBone.GetVertIDArray() --Check to ensure that the verts corrolate to a whole element VertIDBitArray = VertIDArray as BitArray FaceIDBitArray_SetA = MeshOp.GetFacesUsingVert This.MeshNode VertIDBitArray FaceIDBitArray_SetB = MeshOp.GetElementsUsingFace This.MeshNode FaceIDBitArray_SetA --If the 2 bit arrays don't match then we are not mapped to a whole and/or single element. --Quite a convoluted way of comparing bit arrays but there don't seem to be an easier way. if not ( FaceIDBitArray_SetA.NumberSet == FaceIDBitArray_SetB.NumberSet and (FaceIDBitArray_SetA - FaceIDBitArray_SetB).IsEmpty ) then return False --Check there are no conflicts with other bones for SubBone in This.BoneHierarchy.BoneArray do ( --Don't compare to self if SubBone == InputBone then continue if( SubBone.HasConflictingWeights VertIDArray ) then ( return False ) ) return True ), /*--------------------------------------------------- Removes the passed isolated bone from this SkinnedObject */--------------------------------------------------- fn RemoveIsolatedBones BoneRemovalArray = ( --Collapse mesh cache before continuing CollapseMeshCache() --Create a table for how the vert IDs will map out on the new mesh with the bone(s) removed VertIDRemapTable = SkinnedObject_VertIDRemapTable Size:( MeshOp.GetNumVerts MeshNode ) RemoveVertIDBitArray = #{} --Go through each bone gathering the required info and deleting the mesh associated with the bone for SubBone in BoneRemovalArray do ( --Disabling this check for speed. We should have already checked this by this point --if( This.IsIsolatedBone SubBone ) then --( RemoveVertIDArray = SubBone.GetVertIDArray() RemoveVertIDBitArray += RemoveVertIDArray as BitArray VertIDRemapTable.RemoveVerts( RemoveVertIDArray ) --) This.BoneHierarchy.RemoveBone SubBone ) --Process the remap table (condenses the table of verts removing any defunct ones) VertIDRemapTable.ProcessVerts() Meshop.DeleteVerts MeshNode RemoveVertIDBitArray This.BoneHierarchy.ReorderBoneVertIDs VertIDRemapTable ), /*--------------------------------------------------- Creates and returns a new SkinnedObject that contains the passed set of Bones and associated Mesh. Bones must be isolated. */--------------------------------------------------- fn CopyIsolatedBones BoneArray = ( --Collapse mesh cache before continuing CollapseMeshCache() RootSkinnedObject = SkinnedObject() for SubBone in BoneArray do ( --Disabling this check for speed. We should have already checked this by this point --if( IsIsolatedBone SubBone ) then --( CurrentSkinnedObject = SkinnedObject() CurrentSkinnedObject.CreateFromSingleBone SubBone This.MeshNode RootSkinnedObject.MergeSkinnedObject CurrentSkinnedObject --) ) return RootSkinnedObject ), /*--------------------------------------------------- Returns number of bones in this skinned object */--------------------------------------------------- fn BoneCount = ( This.BoneHierarchy.BoneArray.Count ), /*--------------------------------------------------- Makes animated geometry from each of the passed bones. Returns array of newly created animated geometry objects. */--------------------------------------------------- fn ConvertIsolatedBonesToAnimatedGeometry BoneArray = ( --Collapse mesh cache before continuing CollapseMeshCache() AnimatedGeometryArray = #() for SubBone in BoneArray do ( if( IsIsolatedBone SubBone ) then ( --Copy this mesh object. Create a bit array for the inversion of the verts we need. Remove them so we are left with a mesh containing only verts related to the current bone we are working with. ExtractedMesh = RSMesh_Snapshot MeshNode RemoveVertIDBitArray = #{1..MeshNode.NumVerts} - (SubBone.GetVertIDArray() as BitArray) Meshop.DeleteVerts ExtractedMesh RemoveVertIDBitArray --Create a mesh new which clones the animation of the passed bone. ExtractedMesh.Transform = SubBone.Node.Transform ExtractedMesh.Position.Controller = Copy SubBone.Node.Position.Controller ExtractedMesh.Rotation.Controller = Copy SubBone.Node.Rotation.Controller --Add the new mesh object to the return array Append AnimatedGeometryArray ExtractedMesh ) ) return AnimatedGeometryArray ), /*--------------------------------------------------- Sets the transform of the SkinnedObject. Should only be really used on a root SkinnedObject before the merging of other SkinnedObjects other wise weird stuff could happen. */--------------------------------------------------- fn SetTransform InputNode = ( MeshNode.Transform = InputNode.Transform ResetXForm MeshNode ConvertToMesh MeshNode --MeshNode.Scale = [1,1,1] ), /*--------------------------------------------------- Deletes and cleans up this SkinnedObject. Includes the Mesh and Bones. */--------------------------------------------------- fn Destroy = ( This.BoneHierarchy.Destroy() This.ClearMeshCache() if IsValidNode MeshNode then Delete MeshNode ), private /*--------------------------------------------------- Constructor */--------------------------------------------------- fn init = ( MeshNode = Editable_Mesh() BoneHierarchy = SkinnedObject_BoneHierarchy() ), _init = init() ) struct SkinnedObject_VertIDRemapTable ( Size = 1, RemapVertIDArray = #(), --This number designates a vertex that has been removed from the mesh --When we call sort they will be push to the end of the array (what we want) Removed = 2000000000, fn RemoveVerts VertIDArray = ( for VertID in VertIDArray do ( RemapVertIDArray[VertID] = Removed ) ), fn ProcessVerts = ( Sort RemapVertIDArray ), fn GetNewVertID OldVertID = ( for VertID = 1 to RemapVertIDArray.Count do ( if RemapVertIDArray[VertID] == OldVertID then ( return VertID ) ) ), on Create do ( RemapVertIDArray = #{1..Size} as Array ) )