Tools of the Trade: Weight Jumper *UPDATE*

I’ve got some fun new updates for the Weight Jumper tool! First, an additional argument for “percent” is available so you can move less than the full amount between joints. Second, there’s now a GUI! Yay! It’s pretty basic so let’s take a look:

At the top, choose the percentage amount of weight you want shifted between the influences, as well as whether or not to only affect selected vertices. Below that, you can either type in the influence names into their respective fields, or load by selected object. Loading a joint into either field will list the skinClusters registered to it in the list below. The first one is selected by default, but if multiple are present you can select the appropriate one here. Then just transfer away!

You can find the updated files here: Weight Jumper

MMX Progress Report: 004

So I’ve drafted a Blueprint for creating the level efficiently and have mapped out the beginning sections:

I will probably move on to modeling more enemies and getting the new animations in game next. Currently, I’m working on the level entry teleportation effects. I’ve been playing with a few ideas and have settled on this direction. OBS is having some issues (I doubt the 1070 is stressing) so the ignore the tearing and freezing ūüôā

MMX Progress Report: 003

Hi there!

It’s been a while and I apologize for that. Things have been a bit busy here; I’ve traveled across the country and started working for Blizzard! This project has been slowly moving along, but there is a lot to do still. Instead of pics and words I figured a quick video glimpse into the dev environment would show the updates a bit better. Big thanks to Nick Reid for the Dash and Shot effects!

So obviously animations and such need a lot of work. Most already have updates, but those are things I’ll probably tackle last as getting the core mechanics finished off and having a full environment to play in take precedence. Til next time!

MMX Progress Report: 002

Hey all!

I figured it was time for another quick progress update on the state of the game. I’ve been flying around the country recently interviewing with various game studios, so excuse my absence. For this post I wanted to show off the first section of the intro stage bridge. Models and UVs are ready and texture work is next on the list. Other updates are more on the engine side and include new particle charging effects, new controls and camera for the 2D mode, re-scaling of the scene to create a better environment, and the initial drop-in position. For now, here’s a SketchFab viewer of the bridge:

Look for more updates in the coming weeks! I should be able to show off some gameplay pretty soon, as just about all of the systems are in place, and definitely some screenshots of the new effects and textures before then.

Tools of the Trade: Weight Jumper

Howdy folks!

I wanted to write a brief snippet about a new tool called “Weight Jumper” that I’ve now got on GitHub. This tool allows you to move weights from one influence to another on a specific skinCluster, and you can either transfer the entire influence at once (by taking the weights of JointA and adding them to JointB) OR you can select specific vertices to affect. This is great if you need to add in a new extra helper joint, re-draw a joint with the same weighting, or shift weighting from overlapping limbs. Sure, you can try to “paint” out weights but you’ll likely affect more than you intend to. Likewise, the component editor could work if you have a calculator handy and want to waste time. Ever bound a character in A-Pose and the arm moves part of the leg? Instead of carefully painting around the selected area, just grab the verts and move the weights from the arm to the leg! I have a WIP UI that makes this process a breeze, but the single line command is pretty easy too:

weightJumper(skin,jointSource="",jointTarget="",selVerts=False)

If you are transferring a whole joint (for example an IK influence to a new Bind joint), you can select your source and destination, then simply pass a skinCluster and voila!

weightJumper("skinCluster1")

You can pass in the source and destination as well, which are required if you want to use selected vertices. Simply pass true as the final argument:

weightJumper("skinCluster1","C_Spine2_BJ","C_Spine3_BJ",True)

I’ll update this post with the new UI once it is ready, but feel free to take a look at the code now. There are some slower utilities out there using the skinPercent command, but this one uses the Maya Python API and is much, much faster. Enjoy!

Chromebooks vs United Airlines

The current meta for airlines appears to be hosting their own in-flight content for use on your personal devices, namely smartphones and laptops. The exception to this, as I learned recently, is ChromeOS. Chromebooks are becoming increasingly popular for a variety of reasons and it isn’t too uncommon to see them. But there is an odd restriction for United’s In-Flight Entertainment website that attempts to block any laptop that isn’t Windows/OSX from using the service. I did some tinkering on a particularly long flight and found I was quite able to watch their entertainment TV shows and Movies… they’ve simply made it a massive pain to do so.

United decided last year to remove GoGo from the picture (goodbye Chromebook Wifi passes) so the internet wasn’t an option… or so I thought. Let’s take a look at some of the different things you can do while in the sky, starting with the In Flight Entertainment. Once you connect, you’ll find yourself at www.unitedwifi.com. This serves as the main hub for their services. At the bottom, you’ll see a button for “Entertainment” that will do absolutely nothing with mouseover text “javascript:roadblock()”. You can buy internet, or use the other links; the Entertainment one is interestingly the only one that does not work. However, the web directory is setup is a fairly simple way, so after reading how the other pages resolved, I was able to access the Entertainment page here:

https://www.unitedwifi.com/portal/l/entertainment

Now I could browse content, but of course anything I clicked would have a roadblock() call. I lucked out in that one of the movies had a trailer for some reason, and the trailer you are allowed to watch. It resolves to a lengthy link, but using it I was able to reconstruct how to access a movie. When you browse to a movie or TV show, you’ll notice it has an identifier such as “pde/details/EPS_3655_M” (Workaholics). It’s nonsensical how this is set up, but for a TV show, here was the URL I needed:

https://www.unitedwifi.com/portal/l/player?mediaID=EPS_3655&mid=EPS_3655&movieID=EPS_3655_M

Notice how it requires a mediaID, mid, and movieID series of the same key, with the firs two needing the “_M” removed. Admittedly this would have been easier to find if a laptop-user had been nearby, but no matter. Putting in a URL like the above will get a single episode playing without any identifier. No next buttons, etc. It is useless if you want to watch more than a single random episode (though this works fine for movies). The kicker is that the video URL playing has a different resolve that you can find by right clicking it. The above show resolved here:

“http://ufs.ltv/media/TVShows/UWMar16_VI_Workaholics_S5Ep9_Epi.mp4”

That’s right, episode 9. No idea why. Of course, that doesn’t matter because now we have a logical URL, and changing the EP from 9 to 1 works perfectly. Unfortunately, at this point the directory listing itself is “Forbidden” so some Linux magic might be in order to see the other series on their server. Using the same methods above I was able to watch Island Hunters, though for this you might need the episode titles and the format is different:

“http://ufs.ltv/media/TVShows/UWMar16_SC_IslandHunt_Bora_Epi.mp4”

There are movies and shows marked with a “Requires additional software” icon, and those I did not get working. It was a success overall, but a limited one. For the Island Hunters show above, you need episode titles to be able to do anything, right? Let’s talk about what else you can do on United Wifi.

Aside from the UnitedWifi website, for an unknown reason Google.com works, as does web searching. I’ll discuss some caveats below, but you can have a good time with some effort. The first thing to be aware of is that Gmail does not work. I tried changing DNS to no avail, even proxy addresses, so it seems like they’ve whitelisted Google and perhaps a certain range within. While Gmail does not work specifically, a few other services will. Google Voice will open right up, and with a load time so will Hangouts. The chatting doesn’t work quite as well as you’d think, because it seems that after the initial connection a block occurs. Thus, you can receive all of your messages and send a few messages normally, before having to refresh the page to receive the replies. A nuisance, but not game-breaking.

You can also use the web search to find websites you want to visit, then view the Google WebCache to access them. This tends to become text-only — sorry Imgur fans. But remember earlier when we needed the episode names for Island Hunters? Yep, we can find those now. In an ideal world United would simply allow Chromebooks safe passage through their arbitrary blockade, but until (or if) that happens, the above methods can help overcome a few hurdles.

Engines, Mobile, and VR

I’ve been working in both Unity and Unreal for a bit now. I really enjoy working in Unreal for Desktop applications thanks to the Blueprints system and great documentation for it. Having an idea for a useful VR/Mobile app, I decided to step away from Unity and see what Unreal was capable of. ¬†To clarify, I’ll be making this project in both engines, but I’m going to start with Unreal to see how far it cane take me (I already know how to deploy on Unity). The setup so far has been painful. While Unity has a plugin and prefab to drop in, there are significantly more steps to getting this working in Unreal. The greatest drawback I’ve found is build time. A basic level with a single cube and a simple uniform color material took about 7 minutes to build, something that would require a fraction of the time in Unity. ¬†Anyway, I’ve successfully pushed the app to my device, so now I can expand the functionality. Once I have something interesting I’ll start posting images, and see how the UI system in Unreal works.

GitHub Scripts and Updates

I’m in the process of uploading and documenting my tools on GitHub and wanted to take a moment to mention it. The tools I discuss here will be updated there, as well as new ones I haven’t mentioned yet. If you just want to browse the code without downloading and opening the various files GitHub is a great place to go.

There is a link above in the social icons as well as on the main website, but you can find it at www.github.com/CzarOfTheUniverse

Check it out!

Scripting Your Rig: Creating a Three-Joint Chain, Part 2

In the first part of this series, we scripted out¬†locators that can¬†be placed around the scene to create an arm (or leg). Today we’ll continue by¬†turning those locators into a joint chain. If you missed the first post or need a recap, check the link below:

Scripting Your Rig: Creating a Three-Joint Chain, Part 1

Now then, since we setup our file so nicely with a class and a function, let’s make a new function for creating the joints. It’s important to remember that when making rig joints we can’t just spawn arbitrary joints around (like we did in the first post) because the orientations are going to be wrong. In this “createJoints” function, we need to pass in a few arguments to tell it how we want the joints oriented. The specifics won’t matter in this case, but they do need to be consistent. When you orient joints in Maya you choose a primary orientation and an up vector, so let’s make those our arguments:

def createJoints(self, ojVal, saoVal):

Here we’re passing in an orientJoint value (ojVal) and a secondaryAxisOrientation value (saoVal). Not bad, but let’s think one step further shall we? Right now, our createJoints function is missing a key feature in rigging… a name! We did define self.JNTNAMES = [‘Arm’, ‘Elbow’, ‘Wrist’, ‘Twist’] earlier, but that describes the piece, not where it exists. In order to make these uniquely named, we can add a third argument to serve as a prefix. For this case, let’s call it “side”.

def createJoints(self, side, ojVal, saoVal):

Okay, our setup is ready to go! Here are the first few lines:

def createJoints(self, side, ojVal, saoVal):
        """
        Main procedure to create the rig.
        """
        #### Create Some Joints

        self.PREFIX = side

        locs= self.locatorJointsList/code>

For starters, our “side” might not be unique to just the joints. Controllers, groups, or whatever else we write later on might want to reference it. Instead of leaving it a local variable, we bind it to the class scope as self.PREFIX. Similarly, we want that locator list from our other function, but don’t want to reference the massive return name every time, so instead it gets stored as a local variable “locs”.

        j1T = cmds.xform(locs[0], q=1, ws=1, t=1, a=1)
        j2T = cmds.xform(locs[1], q=1, ws=1, t=1, a=1)
        j3T = cmds.xform(locs[2], q=1, ws=1, t=1, a=1)

        j2TL = cmds.xform(locs[1], q=1, os=1, t=1, r=1)
        j3TL = cmds.xform(locs[2], q=1, os=1, t=1, r=1)

Grab the positions of these locators. We need to grab the world space for all three, and the object space additionally for the latter two. Since they are parented, we can use this to find out their relative distances. Now we need to jump briefly into the API!

        j1TV = om.MVector(j1T)
        j2TV = om.MVector(j2T)
        j3TV = om.MVector(j3T)

        lenJ1J2 = om.MVector(j2TV - j1TV).length()
        lenJ2J3 = om.MVector(j3TV - j2TV).length()

Only five lines! Not so scary right? What we’re doing here is creating a 3D Vector out of the world-space translate value for each locator. With vectors, we can quickly perform math operations in 3D space that would be a bit of a pain otherwise. Once all three vectors are established, we can create two new Vectors by subtracting and grabbing the length of that distance. This is similar to saying “5-2” and length being the “=3” end of the equation. The difference of course, is that we’re subtracting 3D coordinate spaces as opposed to simple integers. Anyway, now the length (distance) between joints 1 and 2 as well as between joints 2 and 3 have been saved into variables.

        cmds.select(d=1)
        a1 = cmds.joint(p=j1T, n="%s_%s_BJ" % (self.PREFIX, self.JNTNAMES[0]))
        a2 = cmds.joint(p=(0, j2TL[1], j2TL[2]), r=1, n="%s_%s_BJ" % (self.PREFIX, self.JNTNAMES[1]))
        a3 = cmds.joint(p=(0, j3TL[1], j3TL[2]), r=1, n="%s_%s_BJ" % (self.PREFIX, self.JNTNAMES[2]))

        self.bindJoints = [a1,a2,a3]

Before creating any joints, it’s a good habit to deselect everything. If a joint happens to be selected, you’ll spawn the new joint as a child of that, which we do not want. And using some easy string substitution we’re finally putting those variable names to use! We also need to store those joints as an array to reference later on as self.bindJoints. Time to start orientation!

        cmds.joint(self.bindJoints[0], e=1, oj=ojVal, sao=saoVal, ch=1, zso=1)
        cmds.setAttr("%s.translate%s" % (self.bindJoints[1],ojVal[0].upper()), lenJ1J2)
        cmds.setAttr("%s.translate%s" % (self.bindJoints[2],ojVal[0].upper()), lenJ2J3)

This probably looks a bit janky, so here’s what’s actually happening. We’re grabbing that first joint and orienting it to the values we pass in as arguments. For this example, let’s say “xyz” and “yup” are the arguments, since those tend to be the Maya defaults anyway. Next we’re going to take those lengths we had previously, and position our joints. This is not truly necessary, since we placed those joints on creation, but I’m using it as a sanity check in case the values from Maya were somehow truncated.

        cmds.select(d=1)

        self.ikH = cmds.ikHandle(sj=self.bindJoints[0], ee=self.bindJoints[2], sol="ikRPsolver")[0]
        cmds.xform(self.ikH, t=j3T, a=1)

        pvLoc = cmds.spaceLocator()[0]
        cmds.xform(pvLoc, t=j2T, ws=1, a=1)

        cmds.poleVectorConstraint(pvLoc, self.ikH)   

        #### Create Some More Joints

        cmds.delete(self.ikH, pvLoc)
        cmds.makeIdentity(self.bindJoints[0], t=1, r=1, a=1, jo=0)
        cmds.joint(self.bindJoints[2],e=1,oj='none')

When joints get created in 3D space and not along a single plane, no amount of orienting is going to solve the errors you’ll get because Maya’s Joint Orient only operates in 2D space. Why that’s even an issue after 16+ versions of Maya is anyone’s guess, but here’s my cheap workaround. Starting with the first joint, we temporarily create an ikHandle to the last joint and create a poleVector at the middle joint. This establishes a proper plane of existence for our joint chain. Then those get deleted, the joints get frozen out, and just to make it pretty the last joint gets oriented to “world” which in this case means “parent”. Here’s the final code:

from maya import cmds
import maya.api.OpenMaya as om

class Arm():
    """
    This will create an arm setup.
    """

    def __init__(self):

        self.PART = "Arm"
        self.JNTNAMES = ['Arm', 'Elbow', 'Wrist', 'Twist']
        self.xyz = [[3, 18, 0],[3, 14, -0.25],[3, 10, 0]]

    def placers(self):
        """
        This will create locators for positioning.
        """

        # Create Locators
        self.FirstLoc = cmds.spaceLocator(n=self.JNTNAMES[0])
        cmds.xform(self.FirstLoc, ws=1, t=self.xyz[0])
        self.SecondLoc = cmds.spaceLocator(n=self.JNTNAMES[1])
        cmds.xform(self.SecondLoc, ws=1, t=self.xyz[1])
        self.ThirdLoc = cmds.spaceLocator(n=self.JNTNAMES[2])
        cmds.xform(self.ThirdLoc, ws=1, t=self.xyz[2])

        # Scale for ease of visibility
        for segment in [self.FirstLoc, self.SecondLoc, self.ThirdLoc]:
            cmds.setAttr(segment[0] + ".localScaleX", .2)
            cmds.setAttr(segment[0] + ".localScaleY", .2)
            cmds.setAttr(segment[0] + ".localScaleZ", .2)

        # Parent to maintain angle and move easier
        cmds.parent(self.ThirdLoc, self.SecondLoc)
        cmds.parent(self.SecondLoc, self.FirstLoc)

        # Create pseudo-bones to make it easier to visualize
        cmds.select(d=1)
        tempA = cmds.joint(n="%s_Temp"%self.JNTNAMES[0])

        tempC = cmds.joint(n="%s_Temp"%self.JNTNAMES[1])
        tempD = cmds.joint(n="%s_Temp"%self.JNTNAMES[2])
        cmds.setAttr(tempA + ".overrideEnabled", 1)
        cmds.setAttr(tempA + ".overrideDisplayType", 1)

        cmds.setAttr(tempC + ".overrideEnabled", 1)
        cmds.setAttr(tempC + ".overrideDisplayType", 1)

        cmds.parentConstraint(self.FirstLoc, tempA)
        cmds.parentConstraint(self.SecondLoc, tempC)
        cmds.parentConstraint(self.ThirdLoc, tempD)

        cmds.parent(tempA, self.FirstLoc)

        self.locatorList = [self.FirstLoc, self.SecondLoc, self.ThirdLoc]
        self.locatorJointsList = [tempA, tempC, tempD]
        cmds.select(d=1)
        return self.locatorList, self.locatorJointsList
        
        
    def createJoints(self, side, ojVal, saoVal):
        """
        Main procedure to create the bind joints.
        Requires a side input string (ex. "L")
        Requires a joint orient string (ex. "xyz")
        Requires a secondary axis string (ex. "yup")
        """
        #### Create Some Joints

        self.PREFIX = side

        locs = self.locatorJointsList

        j1T = cmds.xform(locs[0], q=1, ws=1, t=1, a=1)
        j2T = cmds.xform(locs[1], q=1, ws=1, t=1, a=1)
        j3T = cmds.xform(locs[2], q=1, ws=1, t=1, a=1)

        j2TL = cmds.xform(locs[1], q=1, os=1, t=1, r=1)
        j3TL = cmds.xform(locs[2], q=1, os=1, t=1, r=1)

        j1TV = om.MVector(j1T)
        j2TV = om.MVector(j2T)
        j3TV = om.MVector(j3T)

        lenJ1J2 = om.MVector(j2TV - j1TV).length()
        lenJ2J3 = om.MVector(j3TV - j2TV).length()

        cmds.select(d=1)
        a1 = cmds.joint(p=j1T, n="%s_%s_BJ" % (self.PREFIX, self.JNTNAMES[0]))
        a2 = cmds.joint(p=(0, j2TL[1], j2TL[2]), r=1, n="%s_%s_BJ" % (self.PREFIX, self.JNTNAMES[1]))
        a3 = cmds.joint(p=(0, j3TL[1], j3TL[2]), r=1, n="%s_%s_BJ" % (self.PREFIX, self.JNTNAMES[2]))

        self.bindJoints = [a1,a2,a3]

        cmds.joint(self.bindJoints[0], e=1, oj=ojVal, sao=saoVal, ch=1, zso=1)
        cmds.setAttr("%s.translate%s" % (self.bindJoints[1],ojVal[0].upper()), lenJ1J2)
        cmds.setAttr("%s.translate%s" % (self.bindJoints[2],ojVal[0].upper()), lenJ2J3)

        cmds.select(d=1)

        self.ikH = cmds.ikHandle(sj=self.bindJoints[0], ee=self.bindJoints[2], sol="ikRPsolver")[0]
        cmds.xform(self.ikH, t=j3T, a=1)

        pvLoc = cmds.spaceLocator()[0]
        cmds.xform(pvLoc, t=j2T, ws=1, a=1)

        cmds.poleVectorConstraint(pvLoc, self.ikH)   

        #### Create Some More Joints

        cmds.delete(self.ikH, pvLoc)
        cmds.makeIdentity(self.bindJoints[0], t=1, r=1, a=1, jo=0)
        cmds.joint(self.bindJoints[2],e=1,oj='none')

We can test the code by running the following lines:

myNewArm = Arm()
myNewArm.placers()
myNewArm.createJoints("L","xyz","yup")

Tools of the Trade: AttrTransfer

One of the more mundane tasks in Maya¬†is¬†transferring extra attributes from one object to another. If you’ve set up a template of custom attributes on one object, there’s no easy way to copy the attributes and their values over to a new object, or multiple objects. Even more tedious is “jumping connections,”¬†so that ControllerB now controls ObjectC instead of ControllerA. There are various ways to tackle this, from re-parenting shapes to manually re-creating attributes. Also annoying is re-ordering attributes in the ChannelBox. Michael Comet has a great AttrEditor script (and many others!) that got me by for a while, but it breaks on certain attribute types so never quite worked out¬†for me. I decided to write a tool that would remedy all of the above situations:

attrtransfer1

While I hope the usage is pretty straight forward, let’s walk through some of the functionality. As you can see above, I’ve outfitted the high-poly “pCube1” model with every kind of custom attribute. With the tool open, start by loading the selected object as a new source. A full list of attributes that can be transferred and their types will populate the two columns. From there, you can select attributes in the tool and select whatever object(s) you want those attributes to appear on. The checkbox to include connections plays a big role here. If unchecked, the “Transfer” button will simply copy the attributes and their values onto whatever objects you have selected. This means the attributes will still exist on the source object, as well as any connections.

attrtransfer2

Checking on the “Include Connections” option will both delete the attributes from the source and transfer the connections to the new destination object, if any exist. The Channel Box section let’s you directly connect attributes by selecting both objects (driver then targets), and selecting the channels to be driven in the Channel Box. In this section you can also move your custom attributes up or down, and it supports all types. Do keep in mind that String attributes count for this, which is easier to see in the Attribute Editor.

attrtransfer3

That’s about it! You can download this tool from the main website, gitHub, or at the bottom of this post. Do you need more features, have requests, or find a bug? Mail any inquiries to CMiller@CzarOfTheUniverse.com and I’ll be happy to take a look.

Download AttrTransfer