User:.hecko/Modular avatar notes
More actions
This is (a draft of) a companion piece to my example modular avatar (Spawn folder in Resonite), aiming to explain the decisions I've made and aspects not covered by the Rusty Drone standard.
Legs
Since the creator of the standard, Rustybot, doesn’t have legs, there is no documentation about how they should be implemented. However, the PartType parent value for RustyArm snappers has “Leg” and “Foot” as allowed values, somewhat implying that a leg would be a type of arm, and that arms and legs would be compatible. This seems to also be the case with other users of the standard.
As such, I chose to implement the leg sockets in whatever way would allow vanilla arms to work with it. The main part in question is what to set the JointTarget to. While arm sockets can get away with putting it a forearm’s length behind the wrist, there’s no similar static solution for legs. I ended up making an oversimplified IK system to dynamically place the JointTarget where the knee could be, described below.
Assume that the thigh and shin are the same constant length (c), and thus any bend of the knee is symmetrical (isosceles triangle). The midpoint between the leg socket and foot forms a right triangle with the leg socket and the knee, with hypotenuse c. The altitude (a) is half of the distance between the leg socket and foot. We want to know the distance from the midpoint to the knee. Since a^2 + b^2 = c^2, we can get b through sqrt(c^2 - a^2). We offset the JointTarget by this distance in the direction that the foot points toward. (If the foot is further away than the assumed maximum leg length, b might be greater than c, resulting in a negative value. In this case, we clamp the offset to 0, i.e. a fully straight leg, and let the leg deal with that however it pleases. Also, using the foot's forward vector directly may break if the foot points up or down, so it should probably be reduced to the rotation around the axis that runs through the socket and foot.)
This system isn’t ideal. For one, the foot BodyNodes are somewhat above the ground (about 10.5cm in this case), which means vanilla hands won’t touch the ground either. One could apply a static offset, but since the wrist is usually in the vertical center, the hand will then be halfway inside the ground. A proper system would calculate an offset based on the height of the foot, and I might still do that.
Fun fact: despite having legs, this is still technically what people call a “head-and-hands avatar”, i.e. an avatar without a VRIK component. Since VRIK is a notoriously expensive part of avatars (as of writing), this allows the example avatar to be (slightly) more efficient than even the most basic possible full-body avatar.
Self-grab prevention
Since the arm and hand modules are grabbable by default and positioned where your hands are, you’d normally detach them when trying to grab anything else. Ideally, you’d still be able to detach your right arm/hand with your left hand, but not with your right hand.
The solution lies in the CustomCanGrabCheck field of the Grabbable component. Please note that this is approaching forbidden magics; it’s not meant to be edited by the user, as it takes what’s called a “sync delegate” (roughly comparable to a function) that you typically can’t access. However, with a mod like ShowDelegates (needs to be installed by the host), it’s possible to reveal sync delegates built into various components.
One of these components is GripPoseReference, which happens to offer just what we need: a GrabCheck sync delegate that forbids the selected hand. We default to driving HandSide to an out-of-bounds value (disabling it), and to Left or Right only if all of these conditions are met: - the arm/hand is attached (not much point otherwise) - the active user (the user it’s parented under) is equal to the local user (i.e. when they’re wearing the avatar it’s attached to; this also happens when the part is grabbed, but only a frame after you’re already holding it, so it works out) - the local user is in VR (otherwise a screen-mode user would never be able to grab their primary arm/hand, since they only can only use that one) - PartType is equal to Arm or Hand, or not defined (otherwise we’re probably a leg/foot and should be grabbable)
This still doesn’t cover all edge cases. For example, if an unoccupied avatar is held with one’s left hand, they can’t grab its right arm/hand with their right hand, since they become the “active user” of the avatar and thus the part. This is fixable in theory, but would require more and more ProtoFlux for ever nicher scenarios.
Torso mixing
“Mix Torso into Limbs” is an experimental feature that interpolates the rotations of JointTargets with that of the torso, nudging limbs to bebe more parallel to the body. This makes them flail around less, at least with the simulated limb movements of screen mode. I don’t know of any other avatars that implement this, and I don’t know if it’s a positive change for VR users.
Yapper
The Neolander standard defines two dynamic variables, User/RustyTalkingLights (for the color of lights that glow when the user speaks) and User/LightsColor (for the static light color). Since the RustyHead defines no parent values, and since AvatarVoiceSourceAssigner only works if it’s present at the moment of equipment, this is all we can depend on for speech animations. So, how can we make something other than a Rustybot-style glow?
For one, we can go beyond the Neolander standard. Colin The Cat’s Avatar Standard is reasonably well-known, so it’s likely your best bet in terms of compatibility with random people’s avatars. A supporting core would have its AvatarVoiceSourceAssigner set the Avatar/Voice dynamic variable, which you can use for any sort of animation you can dream of.
If you’re seeking maximum compatibility, though, you can be a little clever. As a fallback, the Yapper head divides User/RustyTalkingLights by User/LightsColor, under the assumption that the latter is a volume-driven multiple of the former, and uses the luminance of the result as a “volume” input. This is not ideal by any means; you can’t know what quotient corresponds to what volume, other than 1 probably meaning silence (though you could calibrate the maximum live with some effort), and forget about VisemeAnalyzer. Still, it’s better than nothing… (Incidentally, the first person I tried the Yapper on turned out to be using LightsColor as the dynamic color, with no RustyTalkingLights. Such are the joys of word-of-mouth…)