Skip to content

Instantly share code, notes, and snippets.

@EntranceJew
Last active May 21, 2021 08:13
Show Gist options
  • Save EntranceJew/1e39e83f9978a2389ec6e4823a9ea4af to your computer and use it in GitHub Desktop.
Save EntranceJew/1e39e83f9978a2389ec6e4823a9ea4af to your computer and use it in GitHub Desktop.
the inspector shows BlendShape names but SkinnedMeshRenderer doesn't let me talk to it using them, makes me irate
using System;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// For working with SkinnedMeshRenderer without failing silently or dealing with age old Unity decisions that were
/// <a href="https://forum.unity.com/threads/removing-clamping-of-blendshapes-to-the-range-0-100.504973/">never fully undone</a>.
/// </summary>
///
public static class SkinnedMeshRendererExtensions {
[Flags]
public enum SafeSkinnedMeshRendererAccessModes {
/// <summary>
/// LogError or return silent bad values like Unity does by default.
/// </summary>
None = 0,
/// <summary>
/// Prevents bad Unity behaviors like untracable LogWarnings or silent failure and allows you to catch errors if anything unexpected occurs.
/// </summary>
Safe = 1,
/// <summary>
/// When Unity imports models, it expects BlendShape values between [0,1] and then scales their value to [0,100] unless you change it on the prefab.
/// Effectively, you have to supply a BlendShape weight of 100f to get similar results to having that BlendShape set to 1f in Blender.
/// NOTE: These methods won't work on animated values, unfortunately.
///
/// When you supply this flag:
/// * All reads from the BlendShape will be divided by 100.
/// * All writes to the BlendShape will be multiplied by 100.
/// * This means you will instead be supplying values between [0f,1f].
/// </summary>
Scale100 = 2,
}
/// <summary>
/// Get BlendShape Weight by name with an exception and scaling.
/// Without the "Safe" flag, Unity will return "0" silently. With it, an IndexOutOfRangeException is thrown.
/// Additionally, a KeyNotFoundException will occur if there is no BlendShape by that name.
/// </summary>
/// <param name="smr"></param>
/// <param name="shapeName"></param>
/// <param name="value"></param>
/// <param name="mode"></param>
/// <returns></returns>
/// <exception cref="KeyNotFoundException"></exception>
public static float GetBlendShapeWeightByName(this SkinnedMeshRenderer smr, string shapeName, float value, SafeSkinnedMeshRendererAccessModes mode = SafeSkinnedMeshRendererAccessModes.Safe) {
int index = smr.sharedMesh.GetBlendShapeIndex(shapeName);
if (index == -1 && mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Safe)) {
throw new KeyNotFoundException($"BlendShape by name \"{shapeName}\" did not exit on mesh.");
}
return smr.GetBlendShapeWeightSafe(index, mode);
}
/// <summary>
/// Set BlendShape Weight by name with an exception and scaling.
/// Without the "Safe" flag, Unity causes a LogError without a stack trace. With it, an IndexOutOfRangeException is thrown.
/// Additionally, a KeyNotFoundException will occur if there is no BlendShape by that name.
/// </summary>
/// <param name="smr"></param>
/// <param name="shapeName"></param>
/// <param name="value"></param>
/// <param name="mode"></param>
/// <exception cref="KeyNotFoundException"></exception>
public static void SetBlendShapeWeightByName(this SkinnedMeshRenderer smr, string shapeName, float value, SafeSkinnedMeshRendererAccessModes mode = SafeSkinnedMeshRendererAccessModes.Safe) {
int index = smr.sharedMesh.GetBlendShapeIndex(shapeName);
if (index == -1 && mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Safe)) {
throw new KeyNotFoundException($"BlendShape by name \"{shapeName}\" did not exit on mesh.");
}
smr.SetBlendShapeWeightSafe(index, value, mode);
}
/// <summary>
/// Get BlendShape Weight with an exception and scaling.
/// Without the "Safe" flag, Unity will return "0" silently. With it, an IndexOutOfRangeException is thrown.
/// </summary>
/// <param name="smr"></param>
/// <param name="index"></param>
/// <returns></returns>
/// <exception cref="IndexOutOfRangeException"></exception>
public static float GetBlendShapeWeightSafe(this SkinnedMeshRenderer smr, int index, SafeSkinnedMeshRendererAccessModes mode = SafeSkinnedMeshRendererAccessModes.Safe) {
int shapeCount = smr.sharedMesh.blendShapeCount;
if (mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Safe)) {
if (shapeCount == 0) {
throw new IndexOutOfRangeException("Cannot get BlendShape weight for mesh with zero BlendShapes.");
}
if (index < 0 || index >= shapeCount) {
throw new IndexOutOfRangeException(
$"Cannot get BlendShape weight index {index} for mesh, valid range is: [0,{shapeCount - 1}].");
}
}
float value = smr.GetBlendShapeWeight(index);
if (mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Scale100)) {
value /= 100f;
}
return value;
}
/// <summary>
/// Set BlendShape Weight with an exception and scaling.
/// Without the "Safe" flag, Unity causes a LogError without a stack trace. With it, an IndexOutOfRangeException is thrown.
/// </summary>
/// <param name="smr"></param>
/// <param name="index"></param>
/// <param name="value"></param>
/// <param name="mode"></param>
/// <exception cref="IndexOutOfRangeException"></exception>
public static void SetBlendShapeWeightSafe(this SkinnedMeshRenderer smr, int index, float value, SafeSkinnedMeshRendererAccessModes mode = SafeSkinnedMeshRendererAccessModes.Safe ) {
int shapeCount = smr.sharedMesh.blendShapeCount;
if (mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Safe)) {
if (shapeCount == 0) {
throw new IndexOutOfRangeException("Cannot set BlendShape weight for mesh with zero BlendShapes.");
}
if (index < 0 || index >= shapeCount) {
throw new IndexOutOfRangeException(
$"Cannot set BlendShape weight index {index} for mesh, valid range is: [0,{shapeCount - 1}].");
}
}
if (mode.HasFlag(SafeSkinnedMeshRendererAccessModes.Scale100)) {
value *= 100f;
}
smr.SetBlendShapeWeight(index, value);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment