add Physics support
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2be36e9c0fdfcca44a4ae5dea045b62c
|
||||
guid: 1fdd4de1668fa554d87e5651110ab090
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 577d9725f58264943855b8ac185531fe
|
||||
folderAsset: yes
|
||||
timeCreated: 1466788344
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 14f21d7a1e53a8c4e87b25526a7eb63c
|
||||
folderAsset: yes
|
||||
timeCreated: 1466788345
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aadad8ac54f29e44583510294ac5c312
|
||||
timeCreated: 1466788355
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,76 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a3c684705042f345975d924f6983e36
|
||||
timeCreated: 1466788352
|
||||
licenseType: Store
|
||||
PluginImporter:
|
||||
serializedVersion: 1
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
platformData:
|
||||
Android:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Any:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
Editor:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
Linux:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86
|
||||
Linux64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86_64
|
||||
OSXIntel:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
OSXIntel64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
SamsungTV:
|
||||
enabled: 1
|
||||
settings:
|
||||
STV_MODEL: STANDARD_13
|
||||
Tizen:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
WebGL:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
Win:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Win64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
WindowsStoreApps:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DontProcess: False
|
||||
PlaceholderPath: Assets/JsonDotNet/Assemblies/Standalone/Newtonsoft.Json.dll
|
||||
SDK: AnySDK
|
||||
ScriptingBackend: Il2Cpp
|
||||
iOS:
|
||||
enabled: 1
|
||||
settings:
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
tvOS:
|
||||
enabled: 1
|
||||
settings: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01ef782d02bb1994dbe418b69432552b
|
||||
folderAsset: yes
|
||||
timeCreated: 1466788344
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6807fedb8dcaf04682d2c84f0ab753f
|
||||
timeCreated: 1466788355
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,75 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 17aef65a15b471f468b5fbeb4ff0c6a1
|
||||
timeCreated: 1466788349
|
||||
licenseType: Store
|
||||
PluginImporter:
|
||||
serializedVersion: 1
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
platformData:
|
||||
Android:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Any:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
Editor:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
Linux:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: x86
|
||||
Linux64:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: x86_64
|
||||
LinuxUniversal:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
OSXIntel:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
OSXIntel64:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
OSXUniversal:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
SamsungTV:
|
||||
enabled: 0
|
||||
settings:
|
||||
STV_MODEL: STANDARD_13
|
||||
Win:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Win64:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
WindowsStoreApps:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DontProcess: False
|
||||
PlaceholderPath:
|
||||
SDK: AnySDK
|
||||
ScriptingBackend: Il2Cpp
|
||||
iOS:
|
||||
enabled: 0
|
||||
settings:
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1418141139a6ac443b18cb05c0643a29
|
||||
folderAsset: yes
|
||||
timeCreated: 1466788345
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36f7323c55864364d8bb88c736e4bca6
|
||||
timeCreated: 1466788355
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,67 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9b6ba260dada0ea4a871a42011f8b87d
|
||||
timeCreated: 1466788355
|
||||
licenseType: Store
|
||||
PluginImporter:
|
||||
serializedVersion: 1
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
isPreloaded: 0
|
||||
platformData:
|
||||
Android:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Any:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
Editor:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
Linux:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86
|
||||
Linux64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86_64
|
||||
OSXIntel:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
OSXIntel64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
SamsungTV:
|
||||
enabled: 0
|
||||
settings:
|
||||
STV_MODEL: STANDARD_13
|
||||
Win:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
Win64:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
WindowsStoreApps:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DontProcess: False
|
||||
PlaceholderPath: Assets/JsonDotNet/Assemblies/Standalone/Newtonsoft.Json.dll
|
||||
SDK: AnySDK
|
||||
ScriptingBackend: DotNet
|
||||
iOS:
|
||||
enabled: 0
|
||||
settings:
|
||||
CompileFlags:
|
||||
FrameworkDependencies:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 76f828f47ce26cc43991113c6a39dbbf
|
||||
folderAsset: yes
|
||||
timeCreated: 1466010535
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e7d9a07cc3f02a41a575406e7230846
|
||||
timeCreated: 1466788421
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9a6f8c7c1ea72ce46831c5e1b6150d0c
|
||||
timeCreated: 1466790933
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<linker>
|
||||
<assembly fullname="System">
|
||||
<type fullname="System.ComponentModel.TypeConverter" preserve="all" />
|
||||
<!-- <namespace fullname="System.ComponentModel" preserve="all" /> -->
|
||||
</assembly>
|
||||
</linker>
|
||||
@@ -0,0 +1,6 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 06314f49bdda26043963578d60a0a7ee
|
||||
TextScriptImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,78 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!21 &2100000
|
||||
Material:
|
||||
serializedVersion: 6
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: New Material 1
|
||||
m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_ShaderKeywords:
|
||||
m_LightmapFlags: 4
|
||||
m_EnableInstancingVariants: 0
|
||||
m_DoubleSidedGI: 0
|
||||
m_CustomRenderQueue: -1
|
||||
stringTagMap: {}
|
||||
disabledShaderPasses: []
|
||||
m_SavedProperties:
|
||||
serializedVersion: 3
|
||||
m_TexEnvs:
|
||||
- _BumpMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailAlbedoMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailMask:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _DetailNormalMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _EmissionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 20, y: 20}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MainTex:
|
||||
m_Texture: {fileID: 10309, guid: 0000000000000000f000000000000000, type: 0}
|
||||
m_Scale: {x: 20, y: 20}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _MetallicGlossMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _OcclusionMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
- _ParallaxMap:
|
||||
m_Texture: {fileID: 0}
|
||||
m_Scale: {x: 1, y: 1}
|
||||
m_Offset: {x: 0, y: 0}
|
||||
m_Floats:
|
||||
- _BumpScale: 1
|
||||
- _Cutoff: 0.5
|
||||
- _DetailNormalMapScale: 1
|
||||
- _DstBlend: 0
|
||||
- _GlossMapScale: 1
|
||||
- _Glossiness: 0.5
|
||||
- _GlossyReflections: 1
|
||||
- _Metallic: 0
|
||||
- _Mode: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Parallax: 0.02
|
||||
- _SmoothnessTextureChannel: 0
|
||||
- _SpecularHighlights: 1
|
||||
- _SrcBlend: 1
|
||||
- _UVSec: 0
|
||||
- _ZWrite: 1
|
||||
m_Colors:
|
||||
- _Color: {r: 0.2735849, g: 0.2735849, b: 0.2735849, a: 1}
|
||||
- _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
|
||||
m_BuildTextureStacks: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 00a6b3b340ccb10498d0851a4580bef1
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 2100000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 667e8e9da20031342bb8ca7bfd025c32
|
||||
guid: 8c928dfb7b5a0d249ac7656d0636010d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a8c00ee8c4666954cb9915799f0248d2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 47bf04154c11c5e45b94b1aec2c0baf6
|
||||
folderAsset: yes
|
||||
timeCreated: 1516620867
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d2079bf8fd63d640a121f8fea6f5c5f
|
||||
folderAsset: yes
|
||||
timeCreated: 1516620916
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,498 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for animation bakers, handles timing, keyframing and saving AnimationClips.
|
||||
/// </summary>
|
||||
[HelpURL("http://www.root-motion.com/finalikdox/html/page3.html")]
|
||||
[AddComponentMenu("Scripts/RootMotion/Baker")]
|
||||
public abstract class Baker : MonoBehaviour
|
||||
{
|
||||
|
||||
// Open the User Manual URL
|
||||
[ContextMenu("User Manual")]
|
||||
void OpenUserManual()
|
||||
{
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page3.html");
|
||||
}
|
||||
|
||||
// Open the Script Reference URL
|
||||
[ContextMenu("Scrpt Reference")]
|
||||
void OpenScriptReference()
|
||||
{
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_baker.html");
|
||||
}
|
||||
|
||||
// Link to the Final IK Google Group
|
||||
[ContextMenu("Support Group")]
|
||||
void SupportGroup()
|
||||
{
|
||||
Application.OpenURL("https://groups.google.com/forum/#!forum/final-ik");
|
||||
}
|
||||
|
||||
// Link to the Final IK Asset Store thread in the Unity Community
|
||||
[ContextMenu("Asset Store Thread")]
|
||||
void ASThread()
|
||||
{
|
||||
Application.OpenURL("http://forum.unity3d.com/threads/final-ik-full-body-ik-aim-look-at-fabrik-ccd-ik-1-0-released.222685/");
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public enum Mode
|
||||
{
|
||||
AnimationClips = 0,
|
||||
AnimationStates = 1,
|
||||
PlayableDirector = 2,
|
||||
Realtime = 3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In AnimationClips, AnimationStates or PlayableDirector mode - the frame rate at which the animation clip will be sampled. In Realtime mode - the frame rate at which the pose will be sampled. With the latter, the frame rate is not guaranteed if the player is not able to reach it.
|
||||
/// </summary>
|
||||
[Tooltip("In AnimationClips, AnimationStates or PlayableDirector mode - the frame rate at which the animation clip will be sampled. In Realtime mode - the frame rate at which the pose will be sampled. With the latter, the frame rate is not guaranteed if the player is not able to reach it.")]
|
||||
[Range(1, 90)] public int frameRate = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum allowed error for keyframe reduction.
|
||||
/// </summary>
|
||||
[Tooltip("Maximum allowed error for keyframe reduction.")]
|
||||
[Range(0f, 0.1f)] public float keyReductionError = 0.01f;
|
||||
|
||||
/// <summary>
|
||||
/// AnimationClips mode can be used to bake a batch of AnimationClips directly without the need of setting up an AnimatorController. AnimationStates mode is useful for when you need to set up a more complex rig with layers and AvatarMasks in Mecanim. PlayableDirector mode bakes a Timeline. Realtime mode is for continuous baking of gameplay, ragdoll phsysics or PuppetMaster dynamics.
|
||||
/// </summary>
|
||||
[Tooltip("AnimationClips mode can be used to bake a batch of AnimationClips directly without the need of setting up an AnimatorController. " +
|
||||
"AnimationStates mode is useful for when you need to set up a more complex rig with layers and AvatarMasks in Mecanim. " +
|
||||
"PlayableDirector mode bakes a Timeline. " +
|
||||
"Realtime mode is for continuous baking of gameplay, ragdoll phsysics or PuppetMaster dynamics.")]
|
||||
public Mode mode;
|
||||
|
||||
/// <summary>
|
||||
/// AnimationClips to bake.
|
||||
/// </summary>
|
||||
[Tooltip("AnimationClips to bake.")]
|
||||
public AnimationClip[] animationClips = new AnimationClip[0];
|
||||
|
||||
/// <summary>
|
||||
/// The name of the AnimationStates to bake (must be on the base layer) in the Animator (Right-click on this component header and select 'Find Animation States' to have Baker fill those in automatically, required that state names match with the names of the clips used in them).
|
||||
/// </summary>
|
||||
[Tooltip("The name of the AnimationStates to bake (must be on the base layer) in the Animator above (Right-click on this component header and select 'Find Animation States' to have Baker fill those in automatically, required that state names match with the names of the clips used in them).")]
|
||||
public string[] animationStates = new string[0];
|
||||
|
||||
/// <summary>
|
||||
/// Sets the baked animation clip to loop time and matches the last frame keys with the first. Note that if you are overwriting a previously baked clip, AnimationClipSettings will be copied from the existing clip.
|
||||
/// </summary>
|
||||
[Tooltip("Sets the baked animation clip to loop time and matches the last frame keys with the first. Note that when overwriting a previously baked clip, AnimationClipSettings will be copied from the existing clip.")]
|
||||
public bool loop = true;
|
||||
|
||||
/// <summary>
|
||||
/// The folder to save the baked AnimationClips to.
|
||||
/// </summary>
|
||||
[Tooltip("The folder to save the baked AnimationClips to.")]
|
||||
public string saveToFolder = "Assets";
|
||||
|
||||
/// <summary>
|
||||
/// String that will be added to each clip or animation state name for the saved clip. For example if your animation state/clip names were 'Idle' and 'Walk', then with '_Baked' as Append Name, the Baker will create 'Idle_Baked' and 'Walk_Baked' animation clips.
|
||||
/// </summary>
|
||||
[Tooltip("String that will be added to each clip or animation state name for the saved clip. For example if your animation state/clip names were 'Idle' and 'Walk', then with '_Baked' as Append Name, the Baker will create 'Idle_Baked' and 'Walk_Baked' animation clips.")]
|
||||
public string appendName = "_Baked";
|
||||
|
||||
/// <summary>
|
||||
/// Name of the created AnimationClip file.
|
||||
/// </summary>
|
||||
[Tooltip("Name of the created AnimationClip file.")]
|
||||
public string saveName = "Baked Clip";
|
||||
|
||||
public bool isBaking { get; private set; }
|
||||
public float bakingProgress { get; private set; }
|
||||
|
||||
[SerializeField] [HideInInspector] public Animator animator;
|
||||
[SerializeField] [HideInInspector] public PlayableDirector director;
|
||||
|
||||
protected abstract Transform GetCharacterRoot();
|
||||
protected abstract void OnStartBaking();
|
||||
protected abstract void OnSetLoopFrame(float time);
|
||||
protected abstract void OnSetCurves(ref AnimationClip clip);
|
||||
protected abstract void OnSetKeyframes(float time, bool lastFrame);
|
||||
protected float clipLength { get; private set; }
|
||||
|
||||
#if UNITY_EDITOR
|
||||
private AnimationClip[] bakedClips = new AnimationClip[0];
|
||||
|
||||
private AnimatorStateInfo currentClipStateInfo;
|
||||
private int currentClipIndex;
|
||||
|
||||
private float startBakingTime;
|
||||
private float nextKeyframeTime;
|
||||
private bool firstFrame;
|
||||
private int clipFrames;
|
||||
private int clipFrameNo;
|
||||
private bool setKeyframes;
|
||||
|
||||
private float currentClipTime;
|
||||
private float clipFrameInterval;
|
||||
|
||||
private const bool setLoopFrame = true; // Makes sure first and last keyframes in the baked clip match up if baking a looping clip.
|
||||
#endif
|
||||
|
||||
// Start baking an animation state, clip or timeline, also called for each next clip in the baking array
|
||||
public void BakeClip()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
switch (mode)
|
||||
{
|
||||
case Mode.AnimationClips:
|
||||
AnimationClipSettings originalSettings = AnimationUtility.GetAnimationClipSettings(animationClips[currentClipIndex]);
|
||||
loop = originalSettings.loopTime;
|
||||
break;
|
||||
}
|
||||
|
||||
StartBaking();
|
||||
#endif
|
||||
}
|
||||
|
||||
public void StartBaking()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
isBaking = true;
|
||||
nextKeyframeTime = 0f;
|
||||
|
||||
bakingProgress = 0f;
|
||||
clipFrameNo = 0;
|
||||
|
||||
if (bakedClips.Length == 0)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case Mode.AnimationClips:
|
||||
bakedClips = new AnimationClip[animationClips.Length];
|
||||
break;
|
||||
case Mode.AnimationStates:
|
||||
bakedClips = new AnimationClip[animationStates.Length];
|
||||
break;
|
||||
default:
|
||||
bakedClips = new AnimationClip[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
OnStartBaking();
|
||||
|
||||
firstFrame = true;
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
public void StopBaking()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
if (!isBaking) return;
|
||||
|
||||
if (mode != Mode.Realtime && loop && setLoopFrame)
|
||||
{
|
||||
OnSetLoopFrame(clipLength);
|
||||
}
|
||||
|
||||
bakedClips[currentClipIndex] = new AnimationClip();
|
||||
bakedClips[currentClipIndex].frameRate = frameRate;
|
||||
|
||||
OnSetCurves(ref bakedClips[currentClipIndex]);
|
||||
|
||||
bakedClips[currentClipIndex].EnsureQuaternionContinuity();
|
||||
|
||||
if (mode == Mode.Realtime)
|
||||
{
|
||||
isBaking = false;
|
||||
SaveClips();
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
[ContextMenu("Find Animation States")]
|
||||
public void FindAnimationStates()
|
||||
{
|
||||
animator = GetComponent<Animator>();
|
||||
if (animator == null)
|
||||
{
|
||||
Debug.LogError("No Animator found on Baker GameObject. Can not find animation states.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (animator.runtimeAnimatorController == null)
|
||||
{
|
||||
Debug.LogError("Animator does not have a valid Controller. Can not find animation states.");
|
||||
return;
|
||||
}
|
||||
|
||||
var clips = animator.runtimeAnimatorController.animationClips;
|
||||
animationStates = new string[clips.Length];
|
||||
for (int i = 0; i < clips.Length; i++)
|
||||
{
|
||||
animationStates[i] = clips[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// Baking procedure
|
||||
if (isBaking)
|
||||
{
|
||||
if (mode != Mode.Realtime)
|
||||
{
|
||||
if (firstFrame)
|
||||
{
|
||||
transform.position = Vector3.zero;
|
||||
transform.rotation = Quaternion.identity;
|
||||
GetCharacterRoot().position = Vector3.zero;
|
||||
GetCharacterRoot().rotation = Quaternion.identity;
|
||||
|
||||
StartAnimationUpdate();
|
||||
|
||||
currentClipTime = 0f;
|
||||
firstFrame = false;
|
||||
}
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case Mode.AnimationClips:
|
||||
clipLength = animationClips[currentClipIndex].length;
|
||||
bakingProgress = currentClipTime / clipLength;
|
||||
break;
|
||||
case Mode.AnimationStates:
|
||||
currentClipStateInfo = animator.GetCurrentAnimatorStateInfo(0);
|
||||
bakingProgress = currentClipStateInfo.normalizedTime;
|
||||
|
||||
if (currentClipStateInfo.speed <= 0f)
|
||||
{
|
||||
Debug.LogError("Baker can not bake a clip with 0 speed.");
|
||||
return;
|
||||
}
|
||||
|
||||
clipLength = currentClipStateInfo.length / currentClipStateInfo.speed / animator.speed;
|
||||
break;
|
||||
case Mode.PlayableDirector:
|
||||
clipLength = (float)director.duration;
|
||||
bakingProgress = (float)director.time / clipLength;
|
||||
break;
|
||||
}
|
||||
|
||||
clipFrames = (int)(clipLength * (frameRate));
|
||||
clipFrameInterval = clipLength / (float)(clipFrames);
|
||||
setKeyframes = true;
|
||||
|
||||
// Stop clip baking if the clip is finished, start baking the next clip if possible
|
||||
if (clipFrameNo > clipFrames)
|
||||
{
|
||||
StopBaking();
|
||||
|
||||
currentClipIndex++;
|
||||
if (currentClipIndex >= bakedClips.Length)
|
||||
{
|
||||
currentClipIndex = 0;
|
||||
StopAnimationUpdate();
|
||||
isBaking = false;
|
||||
|
||||
SaveClips();
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
BakeClip();
|
||||
setKeyframes = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!firstFrame) AnimationUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (!isBaking) return;
|
||||
|
||||
if (mode != Mode.Realtime)
|
||||
{
|
||||
if (setKeyframes)
|
||||
{
|
||||
|
||||
OnSetKeyframes(clipFrameNo * clipFrameInterval, clipFrameNo >= clipFrames);
|
||||
|
||||
clipFrameNo++;
|
||||
setKeyframes = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (firstFrame)
|
||||
{
|
||||
startBakingTime = Time.time;
|
||||
firstFrame = false;
|
||||
}
|
||||
|
||||
if (Time.time < nextKeyframeTime) return;
|
||||
|
||||
OnSetKeyframes(Time.time - startBakingTime, false);
|
||||
|
||||
nextKeyframeTime = Time.time + (1f / (float)frameRate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void StartAnimationUpdate()
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case Mode.AnimationClips:
|
||||
if (!AnimationMode.InAnimationMode()) AnimationMode.StartAnimationMode();
|
||||
AnimationMode.BeginSampling();
|
||||
AnimationMode.SampleAnimationClip(gameObject, animationClips[currentClipIndex], 0f);
|
||||
AnimationMode.EndSampling();
|
||||
break;
|
||||
case Mode.AnimationStates:
|
||||
animator.enabled = false;
|
||||
animator.Play(animationStates[currentClipIndex], 0, 0f);
|
||||
break;
|
||||
case Mode.PlayableDirector:
|
||||
director.enabled = false;
|
||||
director.time = 0f;
|
||||
director.Evaluate();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void StopAnimationUpdate()
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case Mode.AnimationClips:
|
||||
if (AnimationMode.InAnimationMode()) AnimationMode.StopAnimationMode();
|
||||
break;
|
||||
case Mode.AnimationStates:
|
||||
animator.enabled = true;
|
||||
break;
|
||||
case Mode.PlayableDirector:
|
||||
director.enabled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void AnimationUpdate()
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case Mode.AnimationClips:
|
||||
|
||||
if (!AnimationMode.InAnimationMode()) AnimationMode.StartAnimationMode();
|
||||
AnimationMode.BeginSampling();
|
||||
AnimationMode.SampleAnimationClip(gameObject, animationClips[currentClipIndex], currentClipTime);
|
||||
AnimationMode.EndSampling();
|
||||
currentClipTime += clipFrameInterval;
|
||||
break;
|
||||
case Mode.AnimationStates:
|
||||
animator.Update(clipFrameInterval);
|
||||
break;
|
||||
case Mode.PlayableDirector:
|
||||
director.time = currentClipTime;
|
||||
director.Evaluate();
|
||||
currentClipTime += clipFrameInterval;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void SaveClips()
|
||||
{
|
||||
var clips = GetBakedClips();
|
||||
AnimationClip savedClip = null;
|
||||
|
||||
for (int i = 0; i < clips.Length; i++)
|
||||
{
|
||||
string path = GetFullPath(i);
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case Baker.Mode.Realtime: break;
|
||||
case Baker.Mode.AnimationClips:
|
||||
AnimationClipSettings originalSettings = AnimationUtility.GetAnimationClipSettings(animationClips[i]);
|
||||
SetClipSettings(clips[i], originalSettings);
|
||||
AnimationUtility.SetAnimationClipSettings(clips[i], originalSettings);
|
||||
break;
|
||||
default:
|
||||
AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(clips[i]);
|
||||
settings.loopTime = loop;
|
||||
SetClipSettings(clips[i], settings);
|
||||
AnimationUtility.SetAnimationClipSettings(clips[i], settings);
|
||||
break;
|
||||
}
|
||||
|
||||
var existing = AssetDatabase.LoadAssetAtPath(path, typeof(AnimationClip)) as AnimationClip;
|
||||
if (existing != null)
|
||||
{
|
||||
// Overwrite with existing settings
|
||||
AnimationClipSettings existingSettings = AnimationUtility.GetAnimationClipSettings(existing);
|
||||
AnimationUtility.SetAnimationClipSettings(clips[i], existingSettings);
|
||||
EditorUtility.CopySerialized(clips[i], existing);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create new asset
|
||||
AssetDatabase.CreateAsset(clips[i], path);
|
||||
}
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
savedClip = AssetDatabase.LoadAssetAtPath(path, typeof(AnimationClip)) as AnimationClip;
|
||||
|
||||
Debug.Log(path + " successfully baked.");
|
||||
}
|
||||
|
||||
Selection.activeObject = savedClip;
|
||||
|
||||
ClearBakedClips();
|
||||
}
|
||||
|
||||
protected virtual void SetClipSettings(AnimationClip clip, AnimationClipSettings settings) {}
|
||||
|
||||
private string GetFullPath(int clipIndex)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case Baker.Mode.AnimationClips:
|
||||
return saveToFolder + "/" + animationClips[clipIndex].name + appendName + ".anim";
|
||||
case Baker.Mode.AnimationStates:
|
||||
return saveToFolder + "/" + animationStates[clipIndex] + appendName + ".anim";
|
||||
case Baker.Mode.PlayableDirector:
|
||||
return saveToFolder + "/" + saveName + ".anim";
|
||||
default:
|
||||
return saveToFolder + "/" + saveName + ".anim";
|
||||
}
|
||||
}
|
||||
|
||||
private AnimationClip[] GetBakedClips()
|
||||
{
|
||||
return bakedClips;
|
||||
}
|
||||
|
||||
private void ClearBakedClips()
|
||||
{
|
||||
bakedClips = new AnimationClip[0];
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ab14efce3fefe4f4982f0fa6911d0cd4
|
||||
timeCreated: 1516617114
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,134 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
/// <summary>
|
||||
/// Baker for Generic/Legacy animation.
|
||||
/// </summary>
|
||||
public class GenericBaker : Baker
|
||||
{
|
||||
/// <summary>
|
||||
/// If true, produced AnimationClips will be marked as Legacy and usable with the Legacy animation system.
|
||||
/// </summary>
|
||||
[Tooltip("If true, produced AnimationClips will be marked as Legacy and usable with the Legacy animation system.")]
|
||||
public bool markAsLegacy;
|
||||
|
||||
/// <summary>
|
||||
/// Root Transform of the hierarchy to bake.
|
||||
/// </summary>
|
||||
[Tooltip("Root Transform of the hierarchy to bake.")]
|
||||
public Transform root;
|
||||
|
||||
/// <summary>
|
||||
/// Root Node used for root motion.
|
||||
/// </summary>
|
||||
[Tooltip("Root Node used for root motion.")]
|
||||
public Transform rootNode;
|
||||
|
||||
/// <summary>
|
||||
/// List of Transforms to ignore, rotation curves will not be baked for these Transforms.
|
||||
/// </summary>
|
||||
[Tooltip("List of Transforms to ignore, rotation curves will not be baked for these Transforms.")]
|
||||
public Transform[] ignoreList;
|
||||
|
||||
/// <summary>
|
||||
/// LocalPosition curves will be baked for these Transforms only. If you are baking a character, the pelvis bone should be added to this array.
|
||||
/// </summary>
|
||||
[Tooltip("LocalPosition curves will be baked for these Transforms only. If you are baking a character, the pelvis bone should be added to this array.")]
|
||||
public Transform[] bakePositionList;
|
||||
|
||||
private BakerTransform[] children = new BakerTransform[0];
|
||||
private BakerTransform rootChild;
|
||||
private int rootChildIndex = -1;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
// Find all the child Transforms of the Animator
|
||||
Transform[] childrenAndRoot = (Transform[])root.GetComponentsInChildren<Transform>();
|
||||
|
||||
children = new BakerTransform[0];
|
||||
|
||||
// Exlude the ignore list, construct the children array
|
||||
for (int i = 0; i < childrenAndRoot.Length; i++)
|
||||
{
|
||||
if (!IsIgnored(childrenAndRoot[i]))
|
||||
{
|
||||
|
||||
Array.Resize(ref children, children.Length + 1);
|
||||
|
||||
bool isRootNode = childrenAndRoot[i] == rootNode;
|
||||
if (isRootNode) rootChildIndex = children.Length - 1;
|
||||
|
||||
children[children.Length - 1] = new BakerTransform(childrenAndRoot[i], root, BakePosition(childrenAndRoot[i]), isRootNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override Transform GetCharacterRoot()
|
||||
{
|
||||
return root;
|
||||
}
|
||||
|
||||
protected override void OnStartBaking()
|
||||
{
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
children[i].Reset();
|
||||
|
||||
if (i == rootChildIndex) children[i].SetRelativeSpace(root.position, root.rotation);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSetLoopFrame(float time)
|
||||
{
|
||||
// TODO Change to SetLoopFrame like in HumanoidBaker
|
||||
for (int i = 0; i < children.Length; i++) children[i].AddLoopFrame(time);
|
||||
}
|
||||
|
||||
protected override void OnSetCurves(ref AnimationClip clip)
|
||||
{
|
||||
// TODO Length Multiplier
|
||||
|
||||
for (int i = 0; i < children.Length; i++) children[i].SetCurves(ref clip);
|
||||
}
|
||||
|
||||
protected override void OnSetKeyframes(float time, bool lastFrame)
|
||||
{
|
||||
for (int i = 0; i < children.Length; i++) children[i].SetKeyframes(time);
|
||||
}
|
||||
|
||||
// Is the specified Transform in the ignore list?
|
||||
private bool IsIgnored(Transform t)
|
||||
{
|
||||
for (int i = 0; i < ignoreList.Length; i++)
|
||||
{
|
||||
if (t == ignoreList[i]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Should we record the localPosition channels of the Transform?
|
||||
private bool BakePosition(Transform t)
|
||||
{
|
||||
for (int i = 0; i < bakePositionList.Length; i++)
|
||||
{
|
||||
if (t == bakePositionList[i]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void SetClipSettings(AnimationClip clip, UnityEditor.AnimationClipSettings settings)
|
||||
{
|
||||
clip.legacy = markAsLegacy;
|
||||
|
||||
if (mode != Baker.Mode.AnimationClips)
|
||||
{
|
||||
clip.wrapMode = loop ? WrapMode.Loop : WrapMode.Default;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 27b32efee52144c4e934e6c3272ceb2c
|
||||
timeCreated: 1516356533
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 15001
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e468ef7e80a87f94588b5eeb3a423b91
|
||||
folderAsset: yes
|
||||
timeCreated: 1516621025
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,90 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
public class TQ
|
||||
{
|
||||
public TQ(Vector3 translation, Quaternion rotation)
|
||||
{
|
||||
t = translation;
|
||||
q = rotation;
|
||||
}
|
||||
|
||||
public Vector3 t;
|
||||
public Quaternion q;
|
||||
}
|
||||
|
||||
/*
|
||||
Written with the kind help of the one commonly known as Mecanim-Dev.
|
||||
*/
|
||||
public class AvatarUtility
|
||||
{
|
||||
|
||||
public static Quaternion GetPostRotation(Avatar avatar, AvatarIKGoal avatarIKGoal)
|
||||
{
|
||||
int humanId = (int)HumanIDFromAvatarIKGoal(avatarIKGoal);
|
||||
if (humanId == (int)HumanBodyBones.LastBone) throw new InvalidOperationException("Invalid human id.");
|
||||
|
||||
MethodInfo methodGetPostRotation = typeof(Avatar).GetMethod("GetPostRotation", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (methodGetPostRotation == null) throw new InvalidOperationException("Cannot find GetPostRotation method.");
|
||||
|
||||
return (Quaternion)methodGetPostRotation.Invoke(avatar, new object[] { humanId });
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get IK position and rotation for foot/hand bone position/rotation.
|
||||
/// </summary>
|
||||
public static TQ GetIKGoalTQ(Avatar avatar, float humanScale, AvatarIKGoal avatarIKGoal, TQ bodyPositionRotation, TQ boneTQ)
|
||||
{
|
||||
int humanId = (int)HumanIDFromAvatarIKGoal(avatarIKGoal);
|
||||
if (humanId == (int)HumanBodyBones.LastBone) throw new InvalidOperationException("Invalid human id.");
|
||||
|
||||
MethodInfo methodGetAxisLength = typeof(Avatar).GetMethod("GetAxisLength", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (methodGetAxisLength == null) throw new InvalidOperationException("Cannot find GetAxisLength method.");
|
||||
|
||||
MethodInfo methodGetPostRotation = typeof(Avatar).GetMethod("GetPostRotation", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (methodGetPostRotation == null) throw new InvalidOperationException("Cannot find GetPostRotation method.");
|
||||
|
||||
Quaternion postRotation = (Quaternion)methodGetPostRotation.Invoke(avatar, new object[] { humanId });
|
||||
|
||||
var goalTQ = new TQ(boneTQ.t, boneTQ.q * postRotation);
|
||||
|
||||
|
||||
if (avatarIKGoal == AvatarIKGoal.LeftFoot || avatarIKGoal == AvatarIKGoal.RightFoot)
|
||||
{
|
||||
// Here you could use animator.leftFeetBottomHeight or animator.rightFeetBottomHeight rather than GetAxisLenght
|
||||
// Both are equivalent but GetAxisLength is the generic way and work for all human bone
|
||||
float axislength = (float)methodGetAxisLength.Invoke(avatar, new object[] { humanId });
|
||||
Vector3 footBottom = new Vector3(axislength, 0, 0);
|
||||
goalTQ.t += (goalTQ.q * footBottom);
|
||||
}
|
||||
|
||||
// IK goal are in avatar body local space
|
||||
Quaternion invRootQ = Quaternion.Inverse(bodyPositionRotation.q);
|
||||
goalTQ.t = invRootQ * (goalTQ.t - bodyPositionRotation.t);
|
||||
goalTQ.q = invRootQ * goalTQ.q;
|
||||
goalTQ.t /= humanScale;
|
||||
|
||||
goalTQ.q = Quaternion.LookRotation(goalTQ.q * Vector3.forward, goalTQ.q * Vector3.up);
|
||||
|
||||
return goalTQ;
|
||||
}
|
||||
|
||||
public static HumanBodyBones HumanIDFromAvatarIKGoal(AvatarIKGoal avatarIKGoal)
|
||||
{
|
||||
HumanBodyBones humanId = HumanBodyBones.LastBone;
|
||||
switch (avatarIKGoal)
|
||||
{
|
||||
case AvatarIKGoal.LeftFoot: humanId = HumanBodyBones.LeftFoot; break;
|
||||
case AvatarIKGoal.RightFoot: humanId = HumanBodyBones.RightFoot; break;
|
||||
case AvatarIKGoal.LeftHand: humanId = HumanBodyBones.LeftHand; break;
|
||||
case AvatarIKGoal.RightHand: humanId = HumanBodyBones.RightHand; break;
|
||||
}
|
||||
return humanId;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f9e403f0f8255c42b90254abc55f528
|
||||
timeCreated: 1516435305
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,571 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
public static class BakerUtilities
|
||||
{
|
||||
|
||||
public static void ReduceKeyframes(AnimationCurve curve, float maxError)
|
||||
{
|
||||
if (maxError <= 0f) return;
|
||||
|
||||
curve.keys = GetReducedKeyframes(curve, maxError);
|
||||
|
||||
// TODO Flatten outTangent for keys that have the next key and testAfter sampled to the same value in the original clip. Same thing for the inTangent
|
||||
}
|
||||
|
||||
public static Keyframe[] GetReducedKeyframes(AnimationCurve curve, float maxError)
|
||||
{
|
||||
Keyframe[] keys = curve.keys;
|
||||
|
||||
int i = 1;
|
||||
while (i < keys.Length - 1 && keys.Length > 2)
|
||||
{
|
||||
Keyframe[] testKeys = new Keyframe[keys.Length - 1];
|
||||
int c = 0;
|
||||
for (int n = 0; n < keys.Length; n++)
|
||||
{
|
||||
if (i != n)
|
||||
{
|
||||
testKeys[c] = new Keyframe(keys[n].time, keys[n].value, keys[n].inTangent, keys[n].outTangent);
|
||||
c++;
|
||||
}
|
||||
}
|
||||
|
||||
AnimationCurve testCurve = new AnimationCurve();
|
||||
testCurve.keys = testKeys;
|
||||
|
||||
float test0 = Mathf.Abs(testCurve.Evaluate(keys[i].time) - keys[i].value);
|
||||
float beforeTime = keys[i].time + (keys[i - 1].time - keys[i].time) * 0.5f;
|
||||
float afterTime = keys[i].time + (keys[i + 1].time - keys[i].time) * 0.5f;
|
||||
|
||||
float testBefore = Mathf.Abs(testCurve.Evaluate(beforeTime) - curve.Evaluate(beforeTime));
|
||||
float testAfter = Mathf.Abs(testCurve.Evaluate(afterTime) - curve.Evaluate(afterTime));
|
||||
|
||||
if (test0 < maxError && testBefore < maxError && testAfter < maxError)
|
||||
{
|
||||
keys = testKeys;
|
||||
}
|
||||
else
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
public static void SetLoopFrame(float time, AnimationCurve curve)
|
||||
{
|
||||
Keyframe[] keys = curve.keys;
|
||||
keys[keys.Length - 1].value = keys[0].value;
|
||||
|
||||
float inTangent = Mathf.Lerp(keys[0].inTangent, keys[keys.Length - 1].inTangent, 0.5f);
|
||||
keys[0].inTangent = inTangent;
|
||||
keys[keys.Length - 1].inTangent = inTangent;
|
||||
|
||||
float outTangent = Mathf.Lerp(keys[0].outTangent, keys[keys.Length - 1].outTangent, 0.5f);
|
||||
keys[0].outTangent = outTangent;
|
||||
keys[keys.Length - 1].outTangent = outTangent;
|
||||
|
||||
keys[keys.Length - 1].time = time;
|
||||
curve.keys = keys;
|
||||
}
|
||||
|
||||
public static void SetTangentMode(AnimationCurve curve)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
if (curve.length < 2) return;
|
||||
|
||||
for (int i = 1; i < curve.length - 1; i++)
|
||||
{
|
||||
UnityEditor.AnimationUtility.SetKeyLeftTangentMode(curve, i, UnityEditor.AnimationUtility.TangentMode.ClampedAuto);
|
||||
UnityEditor.AnimationUtility.SetKeyRightTangentMode(curve, i, UnityEditor.AnimationUtility.TangentMode.ClampedAuto);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
// Realigns quaternion keys to ensure shortest interpolation paths.
|
||||
public static Quaternion EnsureQuaternionContinuity(Quaternion lastQ, Quaternion q)
|
||||
{
|
||||
Quaternion flipped = new Quaternion(-q.x, -q.y, -q.z, -q.w);
|
||||
|
||||
Quaternion midQ = new Quaternion(
|
||||
Mathf.Lerp(lastQ.x, q.x, 0.5f),
|
||||
Mathf.Lerp(lastQ.y, q.y, 0.5f),
|
||||
Mathf.Lerp(lastQ.z, q.z, 0.5f),
|
||||
Mathf.Lerp(lastQ.w, q.w, 0.5f)
|
||||
);
|
||||
|
||||
Quaternion midQFlipped = new Quaternion(
|
||||
Mathf.Lerp(lastQ.x, flipped.x, 0.5f),
|
||||
Mathf.Lerp(lastQ.y, flipped.y, 0.5f),
|
||||
Mathf.Lerp(lastQ.z, flipped.z, 0.5f),
|
||||
Mathf.Lerp(lastQ.w, flipped.w, 0.5f)
|
||||
);
|
||||
|
||||
float angle = Quaternion.Angle(lastQ, midQ);
|
||||
float angleFlipped = Quaternion.Angle(lastQ, midQFlipped);
|
||||
|
||||
return angleFlipped < angle ? flipped : q;
|
||||
}
|
||||
}
|
||||
|
||||
//Manages the Animation Curves for Humanoid Q/T channels.
|
||||
[System.Serializable]
|
||||
public class BakerHumanoidQT
|
||||
{
|
||||
|
||||
private Transform transform;
|
||||
private string Qx, Qy, Qz, Qw;
|
||||
private string Tx, Ty, Tz;
|
||||
|
||||
// Animation curves for each channel of the Transform
|
||||
public AnimationCurve rotX, rotY, rotZ, rotW;
|
||||
public AnimationCurve posX, posY, posZ;
|
||||
|
||||
private AvatarIKGoal goal;
|
||||
private Quaternion lastQ;
|
||||
private bool lastQSet;
|
||||
|
||||
// The custom constructor
|
||||
public BakerHumanoidQT(string name)
|
||||
{
|
||||
Qx = name + "Q.x";
|
||||
Qy = name + "Q.y";
|
||||
Qz = name + "Q.z";
|
||||
Qw = name + "Q.w";
|
||||
|
||||
Tx = name + "T.x";
|
||||
Ty = name + "T.y";
|
||||
Tz = name + "T.z";
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public BakerHumanoidQT(Transform transform, AvatarIKGoal goal, string name)
|
||||
{
|
||||
this.transform = transform;
|
||||
this.goal = goal;
|
||||
|
||||
Qx = name + "Q.x";
|
||||
Qy = name + "Q.y";
|
||||
Qz = name + "Q.z";
|
||||
Qw = name + "Q.w";
|
||||
|
||||
Tx = name + "T.x";
|
||||
Ty = name + "T.y";
|
||||
Tz = name + "T.z";
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
// Clear all curves
|
||||
public void Reset()
|
||||
{
|
||||
rotX = new AnimationCurve();
|
||||
rotY = new AnimationCurve();
|
||||
rotZ = new AnimationCurve();
|
||||
rotW = new AnimationCurve();
|
||||
|
||||
posX = new AnimationCurve();
|
||||
posY = new AnimationCurve();
|
||||
posZ = new AnimationCurve();
|
||||
|
||||
lastQ = Quaternion.identity;
|
||||
lastQSet = false;
|
||||
}
|
||||
|
||||
public void SetIKKeyframes(float time, Avatar avatar, float humanScale, Vector3 bodyPosition, Quaternion bodyRotation)
|
||||
{
|
||||
// TODO Use character scale
|
||||
TQ IKTQ = AvatarUtility.GetIKGoalTQ(avatar, humanScale, goal, new TQ(bodyPosition, bodyRotation), new TQ(transform.position, transform.rotation));
|
||||
|
||||
Quaternion rot = IKTQ.q;
|
||||
if (lastQSet) rot = BakerUtilities.EnsureQuaternionContinuity(lastQ, IKTQ.q);
|
||||
|
||||
//rot.Normalize();
|
||||
|
||||
lastQ = rot;
|
||||
lastQSet = true;
|
||||
|
||||
rotX.AddKey(time, rot.x);
|
||||
rotY.AddKey(time, rot.y);
|
||||
rotZ.AddKey(time, rot.z);
|
||||
rotW.AddKey(time, rot.w);
|
||||
|
||||
Vector3 pos = IKTQ.t;
|
||||
posX.AddKey(time, pos.x);
|
||||
posY.AddKey(time, pos.y);
|
||||
posZ.AddKey(time, pos.z);
|
||||
}
|
||||
|
||||
public void SetKeyframes(float time, Vector3 pos, Quaternion rot)
|
||||
{
|
||||
// Rotation flipping already prevented in HumanoidBaker.UpdateHumanPose().
|
||||
rotX.AddKey(time, rot.x);
|
||||
rotY.AddKey(time, rot.y);
|
||||
rotZ.AddKey(time, rot.z);
|
||||
rotW.AddKey(time, rot.w);
|
||||
|
||||
posX.AddKey(time, pos.x);
|
||||
posY.AddKey(time, pos.y);
|
||||
posZ.AddKey(time, pos.z);
|
||||
}
|
||||
|
||||
public void MoveLastKeyframes(float time)
|
||||
{
|
||||
MoveLastKeyframe(time, rotX);
|
||||
MoveLastKeyframe(time, rotY);
|
||||
MoveLastKeyframe(time, rotZ);
|
||||
MoveLastKeyframe(time, rotW);
|
||||
|
||||
MoveLastKeyframe(time, posX);
|
||||
MoveLastKeyframe(time, posY);
|
||||
MoveLastKeyframe(time, posZ);
|
||||
}
|
||||
|
||||
// Add a copy of the first frame to the specified time
|
||||
public void SetLoopFrame(float time)
|
||||
{
|
||||
BakerUtilities.SetLoopFrame(time, rotX);
|
||||
BakerUtilities.SetLoopFrame(time, rotY);
|
||||
BakerUtilities.SetLoopFrame(time, rotZ);
|
||||
BakerUtilities.SetLoopFrame(time, rotW);
|
||||
|
||||
BakerUtilities.SetLoopFrame(time, posX);
|
||||
BakerUtilities.SetLoopFrame(time, posY);
|
||||
BakerUtilities.SetLoopFrame(time, posZ);
|
||||
}
|
||||
|
||||
private void MoveLastKeyframe(float time, AnimationCurve curve)
|
||||
{
|
||||
Keyframe[] keys = curve.keys;
|
||||
keys[keys.Length - 1].time = time;
|
||||
curve.keys = keys;
|
||||
}
|
||||
|
||||
public void MultiplyLength(AnimationCurve curve, float mlp)
|
||||
{
|
||||
Keyframe[] keys = curve.keys;
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
keys[i].time *= mlp;
|
||||
}
|
||||
curve.keys = keys;
|
||||
}
|
||||
|
||||
// Add curves to the AnimationClip for each channel
|
||||
public void SetCurves(ref AnimationClip clip, float maxError, float lengthMlp)
|
||||
{
|
||||
MultiplyLength(rotX, lengthMlp);
|
||||
MultiplyLength(rotY, lengthMlp);
|
||||
MultiplyLength(rotZ, lengthMlp);
|
||||
MultiplyLength(rotW, lengthMlp);
|
||||
|
||||
MultiplyLength(posX, lengthMlp);
|
||||
MultiplyLength(posY, lengthMlp);
|
||||
MultiplyLength(posZ, lengthMlp);
|
||||
|
||||
BakerUtilities.ReduceKeyframes(rotX, maxError);
|
||||
BakerUtilities.ReduceKeyframes(rotY, maxError);
|
||||
BakerUtilities.ReduceKeyframes(rotZ, maxError);
|
||||
BakerUtilities.ReduceKeyframes(rotW, maxError);
|
||||
|
||||
BakerUtilities.ReduceKeyframes(posX, maxError);
|
||||
BakerUtilities.ReduceKeyframes(posY, maxError);
|
||||
BakerUtilities.ReduceKeyframes(posZ, maxError);
|
||||
|
||||
BakerUtilities.SetTangentMode(rotX);
|
||||
BakerUtilities.SetTangentMode(rotY);
|
||||
BakerUtilities.SetTangentMode(rotZ);
|
||||
BakerUtilities.SetTangentMode(rotW);
|
||||
|
||||
/*
|
||||
BakerUtilities.SetTangentMode(posX);
|
||||
BakerUtilities.SetTangentMode(posY);
|
||||
BakerUtilities.SetTangentMode(posZ);
|
||||
*/
|
||||
|
||||
clip.SetCurve(string.Empty, typeof(Animator), Qx, rotX);
|
||||
clip.SetCurve(string.Empty, typeof(Animator), Qy, rotY);
|
||||
clip.SetCurve(string.Empty, typeof(Animator), Qz, rotZ);
|
||||
clip.SetCurve(string.Empty, typeof(Animator), Qw, rotW);
|
||||
|
||||
clip.SetCurve(string.Empty, typeof(Animator), Tx, posX);
|
||||
clip.SetCurve(string.Empty, typeof(Animator), Ty, posY);
|
||||
clip.SetCurve(string.Empty, typeof(Animator), Tz, posZ);
|
||||
}
|
||||
}
|
||||
|
||||
// Manages the Animation Curves for a single Transform that is a child of the root Transform.
|
||||
[System.Serializable]
|
||||
public class BakerMuscle
|
||||
{
|
||||
|
||||
// Animation curves for each channel of the Transform
|
||||
public AnimationCurve curve;
|
||||
|
||||
private int muscleIndex = -1;
|
||||
private string propertyName;
|
||||
|
||||
// The custom constructor
|
||||
public BakerMuscle(int muscleIndex)
|
||||
{
|
||||
this.muscleIndex = muscleIndex;
|
||||
this.propertyName = MuscleNameToPropertyName(HumanTrait.MuscleName[muscleIndex]);
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
private string MuscleNameToPropertyName(string n)
|
||||
{
|
||||
// Left fingers
|
||||
if (n == "Left Index 1 Stretched") return "LeftHand.Index.1 Stretched";
|
||||
if (n == "Left Index 2 Stretched") return "LeftHand.Index.2 Stretched";
|
||||
if (n == "Left Index 3 Stretched") return "LeftHand.Index.3 Stretched";
|
||||
|
||||
if (n == "Left Middle 1 Stretched") return "LeftHand.Middle.1 Stretched";
|
||||
if (n == "Left Middle 2 Stretched") return "LeftHand.Middle.2 Stretched";
|
||||
if (n == "Left Middle 3 Stretched") return "LeftHand.Middle.3 Stretched";
|
||||
|
||||
if (n == "Left Ring 1 Stretched") return "LeftHand.Ring.1 Stretched";
|
||||
if (n == "Left Ring 2 Stretched") return "LeftHand.Ring.2 Stretched";
|
||||
if (n == "Left Ring 3 Stretched") return "LeftHand.Ring.3 Stretched";
|
||||
|
||||
if (n == "Left Little 1 Stretched") return "LeftHand.Little.1 Stretched";
|
||||
if (n == "Left Little 2 Stretched") return "LeftHand.Little.2 Stretched";
|
||||
if (n == "Left Little 3 Stretched") return "LeftHand.Little.3 Stretched";
|
||||
|
||||
if (n == "Left Thumb 1 Stretched") return "LeftHand.Thumb.1 Stretched";
|
||||
if (n == "Left Thumb 2 Stretched") return "LeftHand.Thumb.2 Stretched";
|
||||
if (n == "Left Thumb 3 Stretched") return "LeftHand.Thumb.3 Stretched";
|
||||
|
||||
if (n == "Left Index Spread") return "LeftHand.Index.Spread";
|
||||
if (n == "Left Middle Spread") return "LeftHand.Middle.Spread";
|
||||
if (n == "Left Ring Spread") return "LeftHand.Ring.Spread";
|
||||
if (n == "Left Little Spread") return "LeftHand.Little.Spread";
|
||||
if (n == "Left Thumb Spread") return "LeftHand.Thumb.Spread";
|
||||
|
||||
// Right fingers
|
||||
if (n == "Right Index 1 Stretched") return "RightHand.Index.1 Stretched";
|
||||
if (n == "Right Index 2 Stretched") return "RightHand.Index.2 Stretched";
|
||||
if (n == "Right Index 3 Stretched") return "RightHand.Index.3 Stretched";
|
||||
|
||||
if (n == "Right Middle 1 Stretched") return "RightHand.Middle.1 Stretched";
|
||||
if (n == "Right Middle 2 Stretched") return "RightHand.Middle.2 Stretched";
|
||||
if (n == "Right Middle 3 Stretched") return "RightHand.Middle.3 Stretched";
|
||||
|
||||
if (n == "Right Ring 1 Stretched") return "RightHand.Ring.1 Stretched";
|
||||
if (n == "Right Ring 2 Stretched") return "RightHand.Ring.2 Stretched";
|
||||
if (n == "Right Ring 3 Stretched") return "RightHand.Ring.3 Stretched";
|
||||
|
||||
if (n == "Right Little 1 Stretched") return "RightHand.Little.1 Stretched";
|
||||
if (n == "Right Little 2 Stretched") return "RightHand.Little.2 Stretched";
|
||||
if (n == "Right Little 3 Stretched") return "RightHand.Little.3 Stretched";
|
||||
|
||||
if (n == "Right Thumb 1 Stretched") return "RightHand.Thumb.1 Stretched";
|
||||
if (n == "Right Thumb 2 Stretched") return "RightHand.Thumb.2 Stretched";
|
||||
if (n == "Right Thumb 3 Stretched") return "RightHand.Thumb.3 Stretched";
|
||||
|
||||
if (n == "Right Index Spread") return "RightHand.Index.Spread";
|
||||
if (n == "Right Middle Spread") return "RightHand.Middle.Spread";
|
||||
if (n == "Right Ring Spread") return "RightHand.Ring.Spread";
|
||||
if (n == "Right Little Spread") return "RightHand.Little.Spread";
|
||||
if (n == "Right Thumb Spread") return "RightHand.Thumb.Spread";
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
public void MultiplyLength(AnimationCurve curve, float mlp)
|
||||
{
|
||||
Keyframe[] keys = curve.keys;
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
keys[i].time *= mlp;
|
||||
}
|
||||
curve.keys = keys;
|
||||
}
|
||||
|
||||
// Add curves to the AnimationClip for each channel
|
||||
public void SetCurves(ref AnimationClip clip, float maxError, float lengthMlp)
|
||||
{
|
||||
MultiplyLength(curve, lengthMlp);
|
||||
BakerUtilities.ReduceKeyframes(curve, maxError);
|
||||
|
||||
// BakerUtilities.SetTangentMode(curve);
|
||||
|
||||
clip.SetCurve(string.Empty, typeof(Animator), propertyName, curve);
|
||||
}
|
||||
|
||||
// Clear all curves
|
||||
public void Reset()
|
||||
{
|
||||
curve = new AnimationCurve();
|
||||
}
|
||||
|
||||
// Record a keyframe for each channel
|
||||
public void SetKeyframe(float time, float[] muscles)
|
||||
{
|
||||
curve.AddKey(time, muscles[muscleIndex]);
|
||||
}
|
||||
|
||||
// Add a copy of the first frame to the specified time
|
||||
public void SetLoopFrame(float time)
|
||||
{
|
||||
BakerUtilities.SetLoopFrame(time, curve);
|
||||
}
|
||||
}
|
||||
|
||||
//Manages the Animation Curves for a single Transform that is a child of the root Transform.
|
||||
[System.Serializable]
|
||||
public class BakerTransform
|
||||
{
|
||||
|
||||
public Transform transform; // The Transform component to record
|
||||
|
||||
// Animation curves for each channel of the Transform
|
||||
public AnimationCurve
|
||||
posX, posY, posZ,
|
||||
rotX, rotY, rotZ, rotW;
|
||||
|
||||
private string relativePath; // Path relative to the root
|
||||
private bool recordPosition; // Should we record the localPosition if the transform?
|
||||
private Vector3 relativePosition;
|
||||
private bool isRootNode;
|
||||
private Quaternion relativeRotation;
|
||||
|
||||
// The custom constructor
|
||||
public BakerTransform(Transform transform, Transform root, bool recordPosition, bool isRootNode)
|
||||
{
|
||||
this.transform = transform;
|
||||
this.recordPosition = recordPosition || isRootNode;
|
||||
this.isRootNode = isRootNode;
|
||||
|
||||
relativePath = string.Empty;
|
||||
#if UNITY_EDITOR
|
||||
relativePath = UnityEditor.AnimationUtility.CalculateTransformPath(transform, root);
|
||||
#endif
|
||||
|
||||
Reset();
|
||||
}
|
||||
|
||||
public void SetRelativeSpace(Vector3 position, Quaternion rotation)
|
||||
{
|
||||
relativePosition = position;
|
||||
relativeRotation = rotation;
|
||||
}
|
||||
|
||||
// Add curves to the AnimationClip for each channel
|
||||
public void SetCurves(ref AnimationClip clip)
|
||||
{
|
||||
if (recordPosition)
|
||||
{
|
||||
clip.SetCurve(relativePath, typeof(Transform), "localPosition.x", posX);
|
||||
clip.SetCurve(relativePath, typeof(Transform), "localPosition.y", posY);
|
||||
clip.SetCurve(relativePath, typeof(Transform), "localPosition.z", posZ);
|
||||
}
|
||||
|
||||
clip.SetCurve(relativePath, typeof(Transform), "localRotation.x", rotX);
|
||||
clip.SetCurve(relativePath, typeof(Transform), "localRotation.y", rotY);
|
||||
clip.SetCurve(relativePath, typeof(Transform), "localRotation.z", rotZ);
|
||||
clip.SetCurve(relativePath, typeof(Transform), "localRotation.w", rotW);
|
||||
|
||||
if (isRootNode) AddRootMotionCurves(ref clip);
|
||||
|
||||
// @todo probably only need to do it once for the clip
|
||||
clip.EnsureQuaternionContinuity(); // DOH!
|
||||
}
|
||||
|
||||
private void AddRootMotionCurves(ref AnimationClip clip)
|
||||
{
|
||||
if (recordPosition)
|
||||
{
|
||||
clip.SetCurve("", typeof(Animator), "MotionT.x", posX);
|
||||
clip.SetCurve("", typeof(Animator), "MotionT.y", posY);
|
||||
clip.SetCurve("", typeof(Animator), "MotionT.z", posZ);
|
||||
}
|
||||
|
||||
clip.SetCurve("", typeof(Animator), "MotionQ.x", rotX);
|
||||
clip.SetCurve("", typeof(Animator), "MotionQ.y", rotY);
|
||||
clip.SetCurve("", typeof(Animator), "MotionQ.z", rotZ);
|
||||
clip.SetCurve("", typeof(Animator), "MotionQ.w", rotW);
|
||||
}
|
||||
|
||||
// Clear all curves
|
||||
public void Reset()
|
||||
{
|
||||
posX = new AnimationCurve();
|
||||
posY = new AnimationCurve();
|
||||
posZ = new AnimationCurve();
|
||||
|
||||
rotX = new AnimationCurve();
|
||||
rotY = new AnimationCurve();
|
||||
rotZ = new AnimationCurve();
|
||||
rotW = new AnimationCurve();
|
||||
}
|
||||
|
||||
public void ReduceKeyframes(float maxError)
|
||||
{
|
||||
BakerUtilities.ReduceKeyframes(rotX, maxError);
|
||||
BakerUtilities.ReduceKeyframes(rotY, maxError);
|
||||
BakerUtilities.ReduceKeyframes(rotZ, maxError);
|
||||
BakerUtilities.ReduceKeyframes(rotW, maxError);
|
||||
|
||||
BakerUtilities.ReduceKeyframes(posX, maxError);
|
||||
BakerUtilities.ReduceKeyframes(posY, maxError);
|
||||
BakerUtilities.ReduceKeyframes(posZ, maxError);
|
||||
}
|
||||
|
||||
// Record a keyframe for each channel
|
||||
public void SetKeyframes(float time)
|
||||
{
|
||||
if (recordPosition)
|
||||
{
|
||||
Vector3 pos = transform.localPosition;
|
||||
|
||||
if (isRootNode)
|
||||
{
|
||||
pos = transform.position - relativePosition;
|
||||
}
|
||||
|
||||
posX.AddKey(time, pos.x);
|
||||
posY.AddKey(time, pos.y);
|
||||
posZ.AddKey(time, pos.z);
|
||||
}
|
||||
|
||||
Quaternion rot = transform.localRotation;
|
||||
|
||||
if (isRootNode)
|
||||
{
|
||||
rot = Quaternion.Inverse(relativeRotation) * transform.rotation;
|
||||
}
|
||||
|
||||
rotX.AddKey(time, rot.x);
|
||||
rotY.AddKey(time, rot.y);
|
||||
rotZ.AddKey(time, rot.z);
|
||||
rotW.AddKey(time, rot.w);
|
||||
}
|
||||
|
||||
// Add a copy of the first frame to the specified time
|
||||
public void AddLoopFrame(float time)
|
||||
{
|
||||
// TODO change to SetLoopFrame
|
||||
if (recordPosition && !isRootNode)
|
||||
{
|
||||
posX.AddKey(time, posX.keys[0].value);
|
||||
posY.AddKey(time, posY.keys[0].value);
|
||||
posZ.AddKey(time, posZ.keys[0].value);
|
||||
}
|
||||
|
||||
rotX.AddKey(time, rotX.keys[0].value);
|
||||
rotY.AddKey(time, rotY.keys[0].value);
|
||||
rotZ.AddKey(time, rotZ.keys[0].value);
|
||||
rotW.AddKey(time, rotW.keys[0].value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 582052db0a6096149a7b2b75914ef3a6
|
||||
timeCreated: 1516263395
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,194 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.Playables;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
/// <summary>
|
||||
/// Baker for Humanoid animation.
|
||||
/// </summary>
|
||||
public class HumanoidBaker : Baker
|
||||
{
|
||||
/// <summary>
|
||||
/// Should the hand IK curves be added to the animation? Disable this if the original hand positions are not important when using the clip on another character via Humanoid retargeting.
|
||||
/// </summary>
|
||||
[Tooltip("Should the hand IK curves be added to the animation? Disable this if the original hand positions are not important when using the clip on another character via Humanoid retargeting.")]
|
||||
public bool bakeHandIK = true;
|
||||
|
||||
/// <summary>
|
||||
/// Max keyframe reduction error for the Root.Q/T, LeftFoot IK and RightFoot IK channels. Having a larger error value for 'Key Reduction Error' and a smaller one for this enables you to optimize clip data size without the floating feet effect by enabling 'Foot IK' in the Animator.
|
||||
/// </summary>
|
||||
[Tooltip("Max keyframe reduction error for the Root.Q/T, LeftFoot IK and RightFoot IK channels. Having a larger error value for 'Key Reduction Error' and a smaller one for this enables you to optimize clip data size without the floating feet effect by enabling 'Foot IK' in the Animator.")]
|
||||
[Range(0f, 0.1f)] public float IKKeyReductionError;
|
||||
|
||||
/// <summary>
|
||||
/// Frame rate divider for the muscle curves. If you had 'Frame Rate' set to 30, and this value set to 3, the muscle curves will be baked at 10 fps. Only the Root Q/T and Hand and Foot IK curves will be baked at 30. This enables you to optimize clip data size without the floating feet effect by enabling 'Foot IK' in the Animator.
|
||||
/// </summary>
|
||||
[Tooltip("Frame rate divider for the muscle curves. If you have 'Frame Rate' set to 30, and this value set to 3, the muscle curves will be baked at 10 fps. Only the Root Q/T and Hand and Foot IK curves will be baked at 30. This enables you to optimize clip data size without the floating feet effect by enabling 'Foot IK' in the Animator.")]
|
||||
[Range(1, 9)] public int muscleFrameRateDiv = 1;
|
||||
|
||||
private BakerMuscle[] bakerMuscles;
|
||||
private BakerHumanoidQT rootQT;
|
||||
private BakerHumanoidQT leftFootQT;
|
||||
private BakerHumanoidQT rightFootQT;
|
||||
private BakerHumanoidQT leftHandQT;
|
||||
private BakerHumanoidQT rightHandQT;
|
||||
|
||||
private float[] muscles = new float[0];
|
||||
private HumanPose pose = new HumanPose();
|
||||
private HumanPoseHandler handler;
|
||||
private Vector3 bodyPosition;
|
||||
private Quaternion bodyRotation = Quaternion.identity;
|
||||
private int mN = 0;
|
||||
private Quaternion lastBodyRotation = Quaternion.identity;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
animator = GetComponent<Animator>();
|
||||
director = GetComponent<PlayableDirector>();
|
||||
|
||||
if (mode == Mode.AnimationStates || mode == Mode.AnimationClips)
|
||||
{
|
||||
if (animator == null || !animator.isHuman)
|
||||
{
|
||||
Debug.LogError("HumanoidBaker GameObject does not have a Humanoid Animator component, can not bake.");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
|
||||
}
|
||||
else if (mode == Mode.PlayableDirector)
|
||||
{
|
||||
if (director == null)
|
||||
{
|
||||
Debug.LogError("HumanoidBaker GameObject does not have a PlayableDirector component, can not bake.");
|
||||
}
|
||||
}
|
||||
|
||||
muscles = new float[HumanTrait.MuscleCount];
|
||||
bakerMuscles = new BakerMuscle[HumanTrait.MuscleCount];
|
||||
for (int i = 0; i < bakerMuscles.Length; i++)
|
||||
{
|
||||
bakerMuscles[i] = new BakerMuscle(i);
|
||||
}
|
||||
|
||||
rootQT = new BakerHumanoidQT("Root");
|
||||
|
||||
leftFootQT = new BakerHumanoidQT(animator.GetBoneTransform(HumanBodyBones.LeftFoot), AvatarIKGoal.LeftFoot, "LeftFoot");
|
||||
rightFootQT = new BakerHumanoidQT(animator.GetBoneTransform(HumanBodyBones.RightFoot), AvatarIKGoal.RightFoot, "RightFoot");
|
||||
leftHandQT = new BakerHumanoidQT(animator.GetBoneTransform(HumanBodyBones.LeftHand), AvatarIKGoal.LeftHand, "LeftHand");
|
||||
rightHandQT = new BakerHumanoidQT(animator.GetBoneTransform(HumanBodyBones.RightHand), AvatarIKGoal.RightHand, "RightHand");
|
||||
|
||||
handler = new HumanPoseHandler(animator.avatar, animator.transform);
|
||||
}
|
||||
|
||||
protected override Transform GetCharacterRoot()
|
||||
{
|
||||
return animator.transform;
|
||||
}
|
||||
|
||||
protected override void OnStartBaking()
|
||||
{
|
||||
|
||||
rootQT.Reset();
|
||||
leftFootQT.Reset();
|
||||
rightFootQT.Reset();
|
||||
leftHandQT.Reset();
|
||||
rightHandQT.Reset();
|
||||
|
||||
for (int i = 0; i < bakerMuscles.Length; i++)
|
||||
{
|
||||
bakerMuscles[i].Reset();
|
||||
}
|
||||
|
||||
mN = muscleFrameRateDiv;
|
||||
|
||||
lastBodyRotation = Quaternion.identity;
|
||||
}
|
||||
|
||||
protected override void OnSetLoopFrame(float time)
|
||||
{
|
||||
for (int i = 0; i < bakerMuscles.Length; i++) bakerMuscles[i].SetLoopFrame(time);
|
||||
|
||||
rootQT.MoveLastKeyframes(time);
|
||||
|
||||
leftFootQT.SetLoopFrame(time);
|
||||
rightFootQT.SetLoopFrame(time);
|
||||
leftHandQT.SetLoopFrame(time);
|
||||
rightHandQT.SetLoopFrame(time);
|
||||
}
|
||||
|
||||
protected override void OnSetCurves(ref AnimationClip clip)
|
||||
{
|
||||
float length = bakerMuscles[0].curve.keys[bakerMuscles[0].curve.keys.Length - 1].time;
|
||||
float lengthMlp = mode != Mode.Realtime ? clipLength / length : 1f;
|
||||
|
||||
for (int i = 0; i < bakerMuscles.Length; i++) bakerMuscles[i].SetCurves(ref clip, keyReductionError, lengthMlp);
|
||||
|
||||
rootQT.SetCurves(ref clip, IKKeyReductionError, lengthMlp);
|
||||
leftFootQT.SetCurves(ref clip, IKKeyReductionError, lengthMlp);
|
||||
rightFootQT.SetCurves(ref clip, IKKeyReductionError, lengthMlp);
|
||||
|
||||
if (bakeHandIK)
|
||||
{
|
||||
leftHandQT.SetCurves(ref clip, IKKeyReductionError, lengthMlp);
|
||||
rightHandQT.SetCurves(ref clip, IKKeyReductionError, lengthMlp);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnSetKeyframes(float time, bool lastFrame)
|
||||
{
|
||||
// Skip muscle frames
|
||||
mN++;
|
||||
bool updateMuscles = true;
|
||||
if (mN < muscleFrameRateDiv && !lastFrame)
|
||||
{
|
||||
updateMuscles = false;
|
||||
}
|
||||
if (mN >= muscleFrameRateDiv) mN = 0;
|
||||
|
||||
UpdateHumanPose();
|
||||
|
||||
if (updateMuscles)
|
||||
{
|
||||
for (int i = 0; i < bakerMuscles.Length; i++) bakerMuscles[i].SetKeyframe(time, muscles);
|
||||
}
|
||||
|
||||
rootQT.SetKeyframes(time, bodyPosition, bodyRotation);
|
||||
|
||||
Vector3 bodyPositionScaled = bodyPosition * animator.humanScale;
|
||||
leftFootQT.SetIKKeyframes(time, animator.avatar, animator.humanScale, bodyPositionScaled, bodyRotation);
|
||||
rightFootQT.SetIKKeyframes(time, animator.avatar, animator.humanScale, bodyPositionScaled, bodyRotation);
|
||||
|
||||
leftHandQT.SetIKKeyframes(time, animator.avatar, animator.humanScale, bodyPositionScaled, bodyRotation);
|
||||
rightHandQT.SetIKKeyframes(time, animator.avatar, animator.humanScale, bodyPositionScaled, bodyRotation);
|
||||
}
|
||||
|
||||
private void UpdateHumanPose()
|
||||
{
|
||||
handler.GetHumanPose(ref pose);
|
||||
|
||||
bodyPosition = pose.bodyPosition;
|
||||
bodyRotation = pose.bodyRotation;
|
||||
|
||||
bodyRotation = BakerUtilities.EnsureQuaternionContinuity(lastBodyRotation, bodyRotation);
|
||||
lastBodyRotation = bodyRotation;
|
||||
|
||||
for (int i = 0; i < pose.muscles.Length; i++)
|
||||
{
|
||||
muscles[i] = pose.muscles[i];
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
protected override void SetClipSettings(AnimationClip clip, UnityEditor.AnimationClipSettings settings)
|
||||
{
|
||||
settings.loopBlendOrientation = true;
|
||||
settings.loopBlendPositionY = true;
|
||||
settings.keepOriginalOrientation = true;
|
||||
settings.keepOriginalPositionY = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: efcc0ec5ed265a340a1412edb9110337
|
||||
timeCreated: 1516287616
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 15000
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c41cf13236c01a747b3dfb963402b3a0
|
||||
folderAsset: yes
|
||||
timeCreated: 1516620909
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4b02f60233a6cf4eb1c424f90323213
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3451f091c656bfc429396cc701ac5b8d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 7400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,69 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!91 &9100000
|
||||
AnimatorController:
|
||||
m_ObjectHideFlags: 0
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_Name: Humanoid Baker Clip Sampler
|
||||
serializedVersion: 5
|
||||
m_AnimatorParameters: []
|
||||
m_AnimatorLayers:
|
||||
- serializedVersion: 5
|
||||
m_Name: Base Layer
|
||||
m_StateMachine: {fileID: 110737558}
|
||||
m_Mask: {fileID: 0}
|
||||
m_Motions: []
|
||||
m_Behaviours: []
|
||||
m_BlendingMode: 0
|
||||
m_SyncedLayerIndex: -1
|
||||
m_DefaultWeight: 0
|
||||
m_IKPass: 0
|
||||
m_SyncedLayerAffectsTiming: 0
|
||||
m_Controller: {fileID: 9100000}
|
||||
--- !u!1102 &110235224
|
||||
AnimatorState:
|
||||
serializedVersion: 5
|
||||
m_ObjectHideFlags: 1
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_Name: Clip 1
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions: []
|
||||
m_StateMachineBehaviours: []
|
||||
m_Position: {x: 50, y: 50, z: 0}
|
||||
m_IKOnFeet: 1
|
||||
m_WriteDefaultValues: 1
|
||||
m_Mirror: 0
|
||||
m_SpeedParameterActive: 0
|
||||
m_MirrorParameterActive: 0
|
||||
m_CycleOffsetParameterActive: 0
|
||||
m_TimeParameterActive: 0
|
||||
m_Motion: {fileID: 7400000, guid: 739b2627204c24240aa40c4f67f59d0e, type: 3}
|
||||
m_Tag:
|
||||
m_SpeedParameter:
|
||||
m_MirrorParameter:
|
||||
m_CycleOffsetParameter:
|
||||
m_TimeParameter:
|
||||
--- !u!1107 &110737558
|
||||
AnimatorStateMachine:
|
||||
serializedVersion: 5
|
||||
m_ObjectHideFlags: 1
|
||||
m_PrefabParentObject: {fileID: 0}
|
||||
m_PrefabInternal: {fileID: 0}
|
||||
m_Name: Base Layer
|
||||
m_ChildStates:
|
||||
- serializedVersion: 1
|
||||
m_State: {fileID: 110235224}
|
||||
m_Position: {x: 300, y: 36, z: 0}
|
||||
m_ChildStateMachines: []
|
||||
m_AnyStateTransitions: []
|
||||
m_EntryTransitions: []
|
||||
m_StateMachineTransitions: {}
|
||||
m_StateMachineBehaviours: []
|
||||
m_AnyStatePosition: {x: 50, y: 20, z: 0}
|
||||
m_EntryPosition: {x: 50, y: 120, z: 0}
|
||||
m_ExitPosition: {x: 800, y: 120, z: 0}
|
||||
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
|
||||
m_DefaultState: {fileID: 110235224}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 24d41cb619dc9dc42bf10705eb128c97
|
||||
timeCreated: 1516284817
|
||||
licenseType: Store
|
||||
NativeFormatImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0bd8d9a8cbacde343967c95c4458318e
|
||||
timeCreated: 1517387718
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3349a7526568cf042bf8609694db3327
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RootMotion.Demos
|
||||
{
|
||||
// Adds simple FK rotation offset to bones.
|
||||
public class FKOffset : MonoBehaviour
|
||||
{
|
||||
[System.Serializable]
|
||||
public class Offset
|
||||
{
|
||||
[HideInInspector][SerializeField] public string name;
|
||||
public HumanBodyBones bone;
|
||||
public Vector3 rotationOffset;
|
||||
|
||||
private Transform t;
|
||||
|
||||
public void Apply(Animator animator)
|
||||
{
|
||||
if (t == null) t = animator.GetBoneTransform(bone);
|
||||
if (t == null) return;
|
||||
|
||||
t.localRotation *= Quaternion.Euler(rotationOffset);
|
||||
}
|
||||
}
|
||||
|
||||
public Offset[] offsets;
|
||||
|
||||
private Animator animator;
|
||||
|
||||
private void Start()
|
||||
{
|
||||
animator = GetComponent<Animator>();
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
foreach (Offset offset in offsets)
|
||||
{
|
||||
offset.Apply(animator);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
foreach (Offset offset in offsets)
|
||||
{
|
||||
offset.name = offset.bone.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea198996b9a58624b82f921e56faf4ba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10875975faa2a3c4f815508f7f376c21
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe88a087b0795174f9c8ec2369f4ccb2
|
||||
folderAsset: yes
|
||||
timeCreated: 1516620937
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,176 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
using UnityEngine.Playables;
|
||||
using System;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
public class BakerInspector : Editor
|
||||
{
|
||||
|
||||
protected void DrawKeyframeSettings(Baker script)
|
||||
{
|
||||
if (script.isBaking) return;
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("frameRate"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("keyReductionError"));
|
||||
}
|
||||
|
||||
protected void DrawModeSettings(Baker script)
|
||||
{
|
||||
if (script.isBaking) return;
|
||||
|
||||
EditorGUILayout.Space();
|
||||
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("mode"));
|
||||
|
||||
switch (script.mode)
|
||||
{
|
||||
case Baker.Mode.AnimationClips:
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("animationClips"), true);
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("appendName"));
|
||||
break;
|
||||
case Baker.Mode.AnimationStates:
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("loop"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("animationStates"), true);
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("appendName"));
|
||||
break;
|
||||
case Baker.Mode.PlayableDirector:
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("loop"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("saveName"));
|
||||
break;
|
||||
default:
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("saveName"));
|
||||
break;
|
||||
}
|
||||
|
||||
//EditorGUILayout.BeginHorizontal();
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField(new GUIContent("Save To Folder"));
|
||||
|
||||
if (EditorGUILayout.DropdownButton(new GUIContent(serializedObject.FindProperty("saveToFolder").stringValue, "The folder to save the baked AnimationClips to."), FocusType.Passive, GUILayout.MaxWidth(400)))
|
||||
{
|
||||
serializedObject.FindProperty("saveToFolder").stringValue = SaveClipFolderPanel.Apply(serializedObject.FindProperty("saveToFolder").stringValue);
|
||||
}
|
||||
//EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
private void TryBake(Baker script)
|
||||
{
|
||||
switch (script.mode)
|
||||
{
|
||||
case Baker.Mode.AnimationClips:
|
||||
if (script.animator == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("No Animator found on Baker GameObject", EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
if (script.animator.isHuman && script.animator.runtimeAnimatorController == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("Humanoid Animator needs to have a valid Controller assigned for clip baking (Unity crash bug)", EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
if (script.animationClips.Length == 0)
|
||||
{
|
||||
EditorGUILayout.LabelField("Please add AnimationClips to bake", EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (AnimationClip clip in script.animationClips)
|
||||
{
|
||||
if (clip == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("One of the AnimationClips is null, can not bake.", EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Bake Animation Clips")) script.BakeClip();
|
||||
return;
|
||||
case Baker.Mode.AnimationStates:
|
||||
if (script.animator == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("No Animator found on Baker GameObject", EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
if (script.animationStates.Length == 0)
|
||||
{
|
||||
EditorGUILayout.LabelField("Please add Animation State names to bake. The Animator must contain AnimationStates with matching names. If AnimationState names match with clip names used in them, you can have the Baker fill the names in automatically by right-clicking on the component header and selecting 'Find Animation States'.", EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < script.animationStates.Length; i++)
|
||||
{
|
||||
if (script.animationStates[i] == string.Empty || script.animationStates[i] == "")
|
||||
{
|
||||
EditorGUILayout.LabelField("One of the animation state names in 'Animation States' is empty, can not bake.", EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Bake Animation States")) script.BakeClip();
|
||||
return;
|
||||
case Baker.Mode.PlayableDirector:
|
||||
if (script.director == null)
|
||||
{
|
||||
EditorGUILayout.LabelField("No PlayableDirector found on Baker GameObject", EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Bake Timeline")) script.BakeClip();
|
||||
break;
|
||||
case Baker.Mode.Realtime:
|
||||
if (GUILayout.Button("Start Baking")) script.StartBaking();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawButtons(Baker script)
|
||||
{
|
||||
if (!script.enabled) return;
|
||||
|
||||
if (script.animator == null)
|
||||
{
|
||||
serializedObject.FindProperty("animator").objectReferenceValue = script.GetComponent<Animator>();
|
||||
}
|
||||
|
||||
if (script.director == null)
|
||||
{
|
||||
serializedObject.FindProperty("director").objectReferenceValue = script.GetComponent<PlayableDirector>();
|
||||
}
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
EditorGUILayout.LabelField("Enter Play Mode to bake.", EditorStyles.helpBox);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!script.isBaking)
|
||||
{
|
||||
TryBake(script);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.red;
|
||||
|
||||
switch (script.mode)
|
||||
{
|
||||
case Baker.Mode.Realtime:
|
||||
if (GUILayout.Button("Stop Baking")) script.StopBaking();
|
||||
break;
|
||||
default:
|
||||
GUILayout.Label("Baking Progress: " + System.Math.Round(script.bakingProgress, 2));
|
||||
break;
|
||||
}
|
||||
|
||||
GUI.color = Color.white;
|
||||
EditorUtility.SetDirty(script);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcf5801ad3ff7fa43b06824f09d82160
|
||||
timeCreated: 1516619065
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
[CustomEditor(typeof(GenericBaker))]
|
||||
public class GenericBakerInspector : BakerInspector
|
||||
{
|
||||
private GenericBaker script { get { return target as GenericBaker; } }
|
||||
|
||||
private MonoScript monoScript;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
// Changing the script execution order
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
monoScript = MonoScript.FromMonoBehaviour(script);
|
||||
int currentExecutionOrder = MonoImporter.GetExecutionOrder(monoScript);
|
||||
if (currentExecutionOrder != 15001) MonoImporter.SetExecutionOrder(monoScript, 15001);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
DrawKeyframeSettings(script as Baker);
|
||||
DrawGenericKeyframeSettings(script);
|
||||
DrawModeSettings(script as Baker);
|
||||
DrawButtons(script as Baker);
|
||||
|
||||
if (serializedObject.ApplyModifiedProperties())
|
||||
{
|
||||
EditorUtility.SetDirty(script);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawGenericKeyframeSettings(GenericBaker script)
|
||||
{
|
||||
if (script.isBaking) return;
|
||||
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("markAsLegacy"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("root"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("rootNode"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("ignoreList"), true);
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("bakePositionList"), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f544a5bc87054e74ac2e415889ba75a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73b96cf8495867d429c9c5b31ff6d6da
|
||||
folderAsset: yes
|
||||
timeCreated: 1516621014
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
public static class AnimationUtilityExtended
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Copies the curves with the specified property names from clipFrom to clipTo.
|
||||
/// </summary>
|
||||
/// <param name="fromClip">copy from clip.</param>
|
||||
/// <param name="toClip">paste to clip</param>
|
||||
/// <param name="propertyNames">Property names ("Root.T", "Root.Q", "LeftFoot.T"...).</param>
|
||||
public static void CopyCurves(AnimationClip fromClip, AnimationClip toClip, string[] propertyNames)
|
||||
{
|
||||
EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings(fromClip);
|
||||
|
||||
for (int i = 0; i < bindings.Length; i++)
|
||||
{
|
||||
for (int n = 0; n < propertyNames.Length; n++)
|
||||
{
|
||||
if (bindings[i].propertyName == propertyNames[n])
|
||||
{
|
||||
CopyCurve(fromClip, toClip, bindings[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void CopyCurve(AnimationClip fromClip, AnimationClip toClip, EditorCurveBinding binding)
|
||||
{
|
||||
AnimationCurve curve = AnimationUtility.GetEditorCurve(fromClip, binding);
|
||||
toClip.SetCurve(string.Empty, typeof(Animator), binding.propertyName, curve);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 054c55a0c5c10dc4fafcfd81aeaaec56
|
||||
timeCreated: 1516620625
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.IO;
|
||||
using System;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
public class SaveClipFolderPanel : EditorWindow
|
||||
{
|
||||
public static string Apply(string currentPath)
|
||||
{
|
||||
string path = EditorUtility.SaveFolderPanel("Save clip(s) to folder", currentPath, "");
|
||||
|
||||
if (path.Length != 0)
|
||||
{
|
||||
return path.Substring(path.IndexOf("Assets/"));
|
||||
}
|
||||
|
||||
return currentPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7edb6cbf0a3b9924d8f631c4c00fd38a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
|
||||
namespace RootMotion
|
||||
{
|
||||
[CustomEditor(typeof(HumanoidBaker))]
|
||||
public class HumanoidBakerInspector : BakerInspector
|
||||
{
|
||||
|
||||
private HumanoidBaker script { get { return target as HumanoidBaker; } }
|
||||
|
||||
private MonoScript monoScript;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
// Changing the script execution order
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
monoScript = MonoScript.FromMonoBehaviour(script);
|
||||
int currentExecutionOrder = MonoImporter.GetExecutionOrder(monoScript);
|
||||
if (currentExecutionOrder != 15000) MonoImporter.SetExecutionOrder(monoScript, 15000);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Move this to BakerInspector.cs
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
serializedObject.Update();
|
||||
|
||||
DrawKeyframeSettings(script as Baker);
|
||||
DrawHumanoidKeyframeSettings(script);
|
||||
DrawModeSettings(script as Baker);
|
||||
DrawButtons(script as Baker);
|
||||
|
||||
if (serializedObject.ApplyModifiedProperties())
|
||||
{
|
||||
EditorUtility.SetDirty(script);
|
||||
}
|
||||
}
|
||||
|
||||
protected void DrawHumanoidKeyframeSettings(HumanoidBaker script)
|
||||
{
|
||||
if (script.isBaking) return;
|
||||
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("IKKeyReductionError"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("muscleFrameRateDiv"));
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("bakeHandIK"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f8139af98945e154885b65eedbb102cc
|
||||
timeCreated: 1516265598
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 100c77ad1e0b943c5b6e6e67b5649801
|
||||
folderAsset: yes
|
||||
timeCreated: 1436186340
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,63 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RootMotion.Dynamics {
|
||||
|
||||
// Custom drawer for BehaviourPuppet MasterProps.
|
||||
[CustomPropertyDrawer (typeof (BehaviourPuppet.MasterProps))]
|
||||
public class BehaviourPuppetMasterPropsDrawer : PropertyDrawer {
|
||||
|
||||
private float lineHeight = 22f;
|
||||
|
||||
public override float GetPropertyHeight(SerializedProperty prop, GUIContent label) {
|
||||
lineHeight = base.GetPropertyHeight(prop, label);
|
||||
|
||||
int enumValueIndex = prop.FindPropertyRelative("normalMode").enumValueIndex;
|
||||
|
||||
switch(enumValueIndex) {
|
||||
case 1: return lineHeight * 2;
|
||||
case 2: return lineHeight * 3;
|
||||
default: return lineHeight;
|
||||
}
|
||||
}
|
||||
|
||||
private void NextLine(ref Rect pos) {
|
||||
pos = EditorGUI.IndentedRect(pos);
|
||||
pos.width -= pos.x - pos.x;
|
||||
pos.x = pos.x;
|
||||
pos.y += lineHeight;
|
||||
pos.height = lineHeight;
|
||||
|
||||
}
|
||||
|
||||
public override void OnGUI (Rect pos, SerializedProperty prop, GUIContent label) {
|
||||
int indent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
EditorGUI.PropertyField (new Rect(pos.x, pos.y, pos.width, lineHeight), prop.FindPropertyRelative ("normalMode"), new GUIContent("Normal Mode", "Active mode keeps the puppet and it's mapping active at all times. " +
|
||||
"Unmapped mode blends in mapping only if the puppet becomes in contact with something and blends out again later to maintain 100% animation accuracy while not in contact. " +
|
||||
"If Kinematic, will set the PuppetMaster.mode to Active when any of the muscles collides with something (static colliders are ignored if 'Activate On Static Collisions' is false) or is hit. PuppetMaster.mode will be set to Kinematic when all muscles are pinned and the puppet is in normal Puppet state. This will increase performance and enable you to have full animation accuracy when PuppetMaster is not needed. Note that collision events are only broadcasted if one of the Rigidbodies is not kinematic, so if you need 2 characters to wake up each other, one has to be active."));
|
||||
|
||||
int enumValueIndex = prop.FindPropertyRelative("normalMode").enumValueIndex;
|
||||
|
||||
switch(enumValueIndex) {
|
||||
case 1:
|
||||
NextLine(ref pos);
|
||||
EditorGUI.PropertyField (pos, prop.FindPropertyRelative ("mappingBlendSpeed"), new GUIContent("Mapping Blend Speed", "The speed of blending in mapping in case of contact."));
|
||||
break;
|
||||
case 2:
|
||||
NextLine(ref pos);
|
||||
EditorGUI.PropertyField (pos, prop.FindPropertyRelative ("activateOnStaticCollisions"), new GUIContent("Activate On Static Collisions", "The speed of blending in mapping in case of contact."));
|
||||
|
||||
NextLine(ref pos);
|
||||
EditorGUI.PropertyField (pos, prop.FindPropertyRelative ("activateOnImpulse"), new GUIContent("Activate On Impulse", "Minimum collision impulse for activating the puppet."));
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
EditorGUI.EndChangeCheck();
|
||||
EditorGUI.indentLevel = indent;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 78a5ae1e71291429f8916d4c02a498b7
|
||||
timeCreated: 1452015016
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,254 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
|
||||
namespace RootMotion.Dynamics
|
||||
{
|
||||
[CustomEditor(typeof(PropMuscle))]
|
||||
public class PropMuscleInspector : Editor
|
||||
{
|
||||
|
||||
private PropMuscle script { get { return target as PropMuscle; } }
|
||||
|
||||
private GUIStyle style = new GUIStyle();
|
||||
private GUIStyle miniLabelStyle = new GUIStyle();
|
||||
private static Color pro = new Color(0.5f, 0.7f, 0.3f, 1f);
|
||||
private static Color free = new Color(0.2f, 0.3f, 0.1f, 1f);
|
||||
private static Color sceneColor = new Color(0.2f, 0.7f, 1f);
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (script == null) return;
|
||||
serializedObject.Update();
|
||||
|
||||
style.wordWrap = true;
|
||||
style.normal.textColor = EditorGUIUtility.isProSkin ? pro : free;
|
||||
|
||||
miniLabelStyle.wordWrap = true;
|
||||
miniLabelStyle.fontSize = 10;
|
||||
miniLabelStyle.normal.textColor = EditorStyles.miniLabel.normal.textColor;
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
if (GUILayout.Button("Remove This Prop Muscle"))
|
||||
{
|
||||
//int index = script.muscle.index;
|
||||
//Debug.Log(index);
|
||||
var puppetMasterS = new SerializedObject(script.puppetMaster);
|
||||
SerializedProperty muscles = puppetMasterS.FindProperty("muscles");
|
||||
|
||||
int index = 0;
|
||||
ConfigurableJoint joint = script.GetComponent<ConfigurableJoint>();
|
||||
for (int i = 0; i < puppetMasterS.FindProperty("muscles").arraySize; i++)
|
||||
{
|
||||
muscles.GetArrayElementAtIndex(i).FindPropertyRelative("index").intValue = i;
|
||||
if (muscles.GetArrayElementAtIndex(i).FindPropertyRelative("joint").objectReferenceValue == joint) index = i;
|
||||
}
|
||||
|
||||
/*
|
||||
if (script.muscle.additionalPin != null)
|
||||
{
|
||||
script.muscle.additionalPin.gameObject.hideFlags = HideFlags.None;
|
||||
script.muscle.additionalPinTarget.gameObject.hideFlags = HideFlags.None;
|
||||
}
|
||||
|
||||
script.muscle.target.gameObject.hideFlags = HideFlags.None;
|
||||
*/
|
||||
|
||||
var target = script.muscle.target;
|
||||
|
||||
|
||||
muscles.DeleteArrayElementAtIndex(index);
|
||||
|
||||
for (int i = 0; i < puppetMasterS.FindProperty("muscles").arraySize; i++)
|
||||
{
|
||||
muscles.GetArrayElementAtIndex(i).FindPropertyRelative("index").intValue = i;
|
||||
}
|
||||
|
||||
puppetMasterS.ApplyModifiedProperties();
|
||||
|
||||
PuppetMasterInspector.DestroyImmediateSafe(target.gameObject);
|
||||
PuppetMasterInspector.DestroyImmediateSafe(script.gameObject);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DrawDefaultInspector();
|
||||
|
||||
//GUILayout.Space(10);
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
if (script.muscle.additionalPin == null)
|
||||
{
|
||||
if (GUILayout.Button("Add Additional Pin"))
|
||||
{
|
||||
AddAdditionalPin();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("Remove Additional Pin"))
|
||||
{
|
||||
RemoveAdditionalPin();
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Additional Pins help to pin this prop muscle to animation from another point. Without it, the prop muscle would be actuated by Muscle Spring only, which is likely not enough to follow fast swinging animations. Move the additional pin by 'Additional Pin Offset' towards the end of the prop (tip of the sword).", miniLabelStyle);
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
}
|
||||
|
||||
private bool AddAdditionalPin()
|
||||
{
|
||||
if (script.muscle.additionalPin != null) return false;
|
||||
|
||||
ConfigurableJoint additionalJoint = null;
|
||||
Transform additionalTarget = null;
|
||||
AddAdditionalPinUnserialized(script, out additionalJoint, out additionalTarget);
|
||||
|
||||
var puppetMasterS = new SerializedObject(script.puppetMaster);
|
||||
puppetMasterS.FindProperty("muscles").GetArrayElementAtIndex(script.muscle.index).FindPropertyRelative("additionalPin").objectReferenceValue = additionalJoint;
|
||||
puppetMasterS.FindProperty("muscles").GetArrayElementAtIndex(script.muscle.index).FindPropertyRelative("additionalPinTarget").objectReferenceValue = additionalTarget;
|
||||
puppetMasterS.ApplyModifiedProperties();
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
//muscle.additionalPin.gameObject.hideFlags = HideFlags.HideInHierarchy;
|
||||
//muscle.additionalPinTarget.gameObject.hideFlags = HideFlags.HideInHierarchy;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void AddAdditionalPinUnserialized(PropMuscle script, out ConfigurableJoint joint, out Transform target)
|
||||
{
|
||||
var addGO = new GameObject("Additional Pin");
|
||||
addGO.gameObject.layer = script.gameObject.layer;
|
||||
addGO.transform.parent = script.transform;
|
||||
addGO.transform.localPosition = script.additionalPinOffset;
|
||||
addGO.transform.localRotation = Quaternion.identity;
|
||||
//script.lastAdditionalPinOffset = additionalPinOffset;
|
||||
|
||||
addGO.AddComponent<Rigidbody>();
|
||||
|
||||
joint = addGO.AddComponent<ConfigurableJoint>();
|
||||
joint.connectedBody = script.muscle.joint.GetComponent<Rigidbody>();
|
||||
joint.autoConfigureConnectedAnchor = false;
|
||||
joint.connectedAnchor = script.additionalPinOffset;
|
||||
joint.xMotion = ConfigurableJointMotion.Locked;
|
||||
joint.yMotion = ConfigurableJointMotion.Locked;
|
||||
joint.zMotion = ConfigurableJointMotion.Locked;
|
||||
joint.angularXMotion = ConfigurableJointMotion.Locked;
|
||||
joint.angularYMotion = ConfigurableJointMotion.Locked;
|
||||
joint.angularZMotion = ConfigurableJointMotion.Locked;
|
||||
|
||||
var addTargetGO = new GameObject("Additional Pin Target");
|
||||
addTargetGO.layer = script.muscle.target.gameObject.layer;
|
||||
addTargetGO.transform.parent = script.muscle.target;
|
||||
addTargetGO.transform.position = addGO.transform.position;
|
||||
addTargetGO.transform.rotation = addGO.transform.rotation;
|
||||
target = addTargetGO.transform;
|
||||
}
|
||||
|
||||
private bool RemoveAdditionalPin()
|
||||
{
|
||||
if (script.muscle.additionalPin == null) return false;
|
||||
|
||||
//script.muscle.additionalPin.gameObject.hideFlags = HideFlags.None;
|
||||
//script.muscle.additionalPinTarget.gameObject.hideFlags = HideFlags.None;
|
||||
|
||||
PuppetMasterInspector.DestroyImmediateSafe(script.muscle.additionalPin.gameObject);
|
||||
PuppetMasterInspector.DestroyImmediateSafe(script.muscle.additionalPinTarget.gameObject);
|
||||
|
||||
var puppetMasterS = new SerializedObject(script.puppetMaster);
|
||||
puppetMasterS.FindProperty("muscles").GetArrayElementAtIndex(script.muscle.index).FindPropertyRelative("additionalPin").objectReferenceValue = null;
|
||||
puppetMasterS.FindProperty("muscles").GetArrayElementAtIndex(script.muscle.index).FindPropertyRelative("additionalPinTarget").objectReferenceValue = null;
|
||||
puppetMasterS.ApplyModifiedProperties();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static float GetHandleSize(Vector3 position)
|
||||
{
|
||||
float s = HandleUtility.GetHandleSize(position) * 0.1f;
|
||||
return Mathf.Lerp(s, 0.025f, 0.2f);
|
||||
}
|
||||
|
||||
void OnSceneGUI() {
|
||||
DrawScene(script);
|
||||
}
|
||||
|
||||
private static void CubeCapSafe(Vector3 position, Quaternion rotation, float size)
|
||||
{
|
||||
#if UNITY_5_6_OR_NEWER
|
||||
Handles.CubeHandleCap(0, position, rotation, size, EventType.Repaint);
|
||||
#else
|
||||
Handles.CubeCap(0, position, rotation, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void SphereCapSafe(Vector3 position, Quaternion rotation, float size)
|
||||
{
|
||||
#if UNITY_5_6_OR_NEWER
|
||||
Handles.SphereHandleCap(0, position, rotation, size, EventType.Repaint);
|
||||
#else
|
||||
Handles.SphereCap(0, position, rotation, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void DrawScene(PropMuscle script)
|
||||
{
|
||||
if (script == null) return;
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
GUIStyle sceneLabelStyle = new GUIStyle();
|
||||
sceneLabelStyle.wordWrap = false;
|
||||
sceneLabelStyle.normal.textColor = sceneColor;
|
||||
|
||||
Handles.color = sceneColor;
|
||||
float size = GetHandleSize(script.transform.position);
|
||||
|
||||
if (Selection.activeGameObject == script.gameObject)
|
||||
{
|
||||
Handles.Label(script.transform.position + script.transform.forward * size * 2f, new GUIContent(script.transform.name), sceneLabelStyle);
|
||||
} else
|
||||
{
|
||||
Handles.Label(script.transform.position + script.transform.forward * size * 2f, new GUIContent("Click to select Prop Muscle"), sceneLabelStyle);
|
||||
}
|
||||
|
||||
if (Selection.activeGameObject == script.gameObject)
|
||||
{
|
||||
CubeCapSafe(script.transform.position, script.transform.rotation, size * 2f);
|
||||
} else
|
||||
{
|
||||
if (DotButton(script.transform.position, script.transform.rotation, size, size))
|
||||
{
|
||||
Selection.activeGameObject = script.gameObject;
|
||||
}
|
||||
}
|
||||
|
||||
if (script.muscle.additionalPin != null)
|
||||
{
|
||||
Handles.DrawLine(script.transform.position, script.muscle.additionalPin.transform.position);
|
||||
SphereCapSafe(script.muscle.additionalPin.transform.position, script.muscle.additionalPin.transform.rotation, size);
|
||||
|
||||
if (Selection.activeGameObject == script.gameObject)
|
||||
{
|
||||
Handles.Label(script.muscle.additionalPin.transform.position + script.transform.forward * size * 2f, new GUIContent("Additional Pin"), sceneLabelStyle);
|
||||
}
|
||||
}
|
||||
|
||||
Handles.color = Color.white;
|
||||
}
|
||||
|
||||
public static bool DotButton(Vector3 position, Quaternion direction, float size, float pickSize)
|
||||
{
|
||||
#if UNITY_5_6_OR_NEWER
|
||||
return Handles.Button(position, direction, size, pickSize, Handles.DotHandleCap);
|
||||
#else
|
||||
return Handles.Button(position, direction, size, pickSize, Handles.DotCap);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d0583daa68217f49b271ee02c9374f5
|
||||
timeCreated: 1550562021
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,21 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RootMotion.Dynamics {
|
||||
|
||||
[CustomEditor(typeof(PuppetMasterHumanoidConfig))]
|
||||
public class PuppetMasterHumanoidConfigInspector : Editor {
|
||||
|
||||
private PuppetMasterHumanoidConfig script { get { return target as PuppetMasterHumanoidConfig; } }
|
||||
|
||||
public override void OnInspectorGUI() {
|
||||
foreach (PuppetMasterHumanoidConfig.HumanoidMuscle m in script.muscles) {
|
||||
m.name = m.bone.ToString();
|
||||
}
|
||||
|
||||
DrawDefaultInspector ();
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f97a5dd098e6d624caf82f29ed6896d2
|
||||
timeCreated: 1485252527
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,437 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion.Dynamics
|
||||
{
|
||||
|
||||
[CustomEditor(typeof(PuppetMaster))]
|
||||
public class PuppetMasterInspector : Editor
|
||||
{
|
||||
|
||||
private PuppetMaster script { get { return target as PuppetMaster; } }
|
||||
private Transform animatedCharacter;
|
||||
private bool isValid;
|
||||
private int characterControllerLayer = 8;
|
||||
private int ragdollLayer = 9;
|
||||
private ConfigurableJoint addPropMuscleTo;
|
||||
private MonoScript monoScript;
|
||||
private Animator targetAnimator;
|
||||
private SkinnedMeshRenderer[] skinnedMeshRenderers = new SkinnedMeshRenderer[0];
|
||||
|
||||
// Colors
|
||||
private GUIStyle style = new GUIStyle();
|
||||
private GUIStyle miniLabelStyle = new GUIStyle();
|
||||
private static Color pro = new Color(0.5f, 0.7f, 0.3f, 1f);
|
||||
private static Color free = new Color(0.2f, 0.3f, 0.1f, 1f);
|
||||
|
||||
#region Inspector
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
monoScript = MonoScript.FromMonoBehaviour(script);
|
||||
int currentExecutionOrder = MonoImporter.GetExecutionOrder(monoScript);
|
||||
if (currentExecutionOrder != 10100) MonoImporter.SetExecutionOrder(monoScript, 10100);
|
||||
|
||||
// Fix negative collider sizes for ragdolls created before PuppetMaster 0.3
|
||||
BoxCollider[] boxColliders = script.GetComponentsInChildren<BoxCollider>();
|
||||
foreach (BoxCollider box in boxColliders)
|
||||
{
|
||||
box.size = new Vector3(Mathf.Abs(box.size.x), Mathf.Abs(box.size.y), Mathf.Abs(box.size.z));
|
||||
}
|
||||
|
||||
CapsuleCollider[] capsuleColliders = script.GetComponentsInChildren<CapsuleCollider>();
|
||||
foreach (CapsuleCollider capsule in capsuleColliders)
|
||||
{
|
||||
capsule.height = Mathf.Abs(capsule.height);
|
||||
capsule.radius = Mathf.Abs(capsule.radius);
|
||||
}
|
||||
|
||||
if (script.transform.parent != null)
|
||||
{
|
||||
targetAnimator = script.transform.parent.GetComponentInChildren<Animator>();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (script.initiated) targetAnimator = script.targetAnimator;
|
||||
}
|
||||
|
||||
if (script.transform.parent != null)
|
||||
{
|
||||
skinnedMeshRenderers = script.transform.parent.GetComponentsInChildren<SkinnedMeshRenderer>();
|
||||
}
|
||||
|
||||
isValid = IsValid();
|
||||
}
|
||||
|
||||
private bool IsValid()
|
||||
{
|
||||
if (script.muscles.Length > 0) return true;
|
||||
|
||||
ConfigurableJoint[] joints = script.gameObject.GetComponentsInChildren<ConfigurableJoint>();
|
||||
|
||||
if (joints == null || joints.Length == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (animatedCharacter == null) animatedCharacter = script.transform;
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (script == null) return;
|
||||
serializedObject.Update();
|
||||
|
||||
if (script.muscles.Length > 0 && script.muscles[0].target != null && script.targetRoot == null) script.targetRoot = script.FindTargetRootRecursive(script.muscles[0].target);
|
||||
|
||||
style.wordWrap = true;
|
||||
style.normal.textColor = EditorGUIUtility.isProSkin ? pro : free;
|
||||
|
||||
miniLabelStyle.wordWrap = true;
|
||||
miniLabelStyle.fontSize = 10;
|
||||
miniLabelStyle.normal.textColor = EditorStyles.miniLabel.normal.textColor;
|
||||
|
||||
if (!isValid)
|
||||
{
|
||||
EditorGUILayout.Space();
|
||||
EditorGUILayout.LabelField(
|
||||
"To set up PuppetMaster, add the component to a ragdoll character that is built with ConfigurableJoints. " +
|
||||
"If your ragdoll uses other types of joints, you can easily convert them by selecting the character and clicking on GameObject/Convert To ConfigurableJoints."
|
||||
, style);
|
||||
EditorGUILayout.Space();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
for (int i = 0; i < script.muscles.Length; i++)
|
||||
{
|
||||
script.muscles[i].index = i;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Muscle m in script.muscles)
|
||||
{
|
||||
if (m.joint == null)
|
||||
{
|
||||
m.name = "Missing Joint Reference!";
|
||||
}
|
||||
else if (m.target == null)
|
||||
{
|
||||
m.name = "Missing Target Reference!";
|
||||
}
|
||||
else m.name = m.joint.name;
|
||||
}
|
||||
|
||||
if (script.muscles.Length == 0)
|
||||
{
|
||||
GUILayout.Space(5);
|
||||
|
||||
animatedCharacter = (Transform)EditorGUILayout.ObjectField(new GUIContent("Animated Target", "If it is assigned to the same GameObject, it will be duplicated and cleaned up of all the ragdoll components. It can also be an instance of the character or even another character as long as the positions of the bones match. The rotations of the bones don't need to be identical. That makes it possible to share ragdoll structures and is also most useful when the feet bones are not aligned to the ground (like Mixamo characters). In that case you can simply rotate the ragdoll's feet to align, keeping the target's feet where they are."), animatedCharacter, typeof(Transform), true);
|
||||
|
||||
GUILayout.Space(5);
|
||||
characterControllerLayer = EditorGUILayout.IntField(new GUIContent("Character Controller Layer", "The layer to assign the character controller to. Collisions between this layer and the 'Ragdoll Layer' will be ignored, or else the ragdoll would collide with the character controller."), characterControllerLayer);
|
||||
ragdollLayer = EditorGUILayout.IntField(new GUIContent("Ragdoll Layer", "The layer to assign the PuppetMaster and all it's muscles to. Collisions between this layer and the 'Character Controller Layer' will be ignored, or else the ragdoll would collide with the character controller."), ragdollLayer);
|
||||
|
||||
if (characterControllerLayer == ragdollLayer)
|
||||
{
|
||||
GUILayout.Space(5);
|
||||
EditorGUILayout.LabelField(
|
||||
"The 'Character Controller Layer' must not be the same as the 'Ragdoll Layer', your ragdoll bones would collide with the character controller."
|
||||
, style);
|
||||
EditorGUILayout.Space();
|
||||
}
|
||||
|
||||
if (animatedCharacter != null && characterControllerLayer != ragdollLayer)
|
||||
{
|
||||
GUILayout.Space(5);
|
||||
|
||||
if (GUILayout.Button("Set Up PuppetMaster", GUILayout.MaxWidth(140)))
|
||||
{
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
if (animatedCharacter == script.transform)
|
||||
{
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(animatedCharacter.gameObject)) PrefabUtility.UnpackPrefabInstance(PrefabUtility.GetNearestPrefabInstanceRoot(animatedCharacter.gameObject), PrefabUnpackMode.Completely, InteractionMode.UserAction);
|
||||
} else
|
||||
{
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(script.gameObject)) PrefabUtility.UnpackPrefabInstance(PrefabUtility.GetNearestPrefabInstanceRoot(script.gameObject), PrefabUnpackMode.Completely, InteractionMode.UserAction);
|
||||
}
|
||||
#endif
|
||||
script.SetUpTo(animatedCharacter, characterControllerLayer, ragdollLayer);
|
||||
|
||||
animatedCharacter = null;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Setting up PuppetMaster is not undoable. Make sure to make a backup duplicate of the character before you do that.", miniLabelStyle);
|
||||
|
||||
if (animatedCharacter == script.transform)
|
||||
{
|
||||
GUILayout.Space(5);
|
||||
EditorGUILayout.BeginVertical("Box");
|
||||
GUILayout.Label("NB! 'Animated Target' is set to the same GameObject as this. PuppetMaster will make a duplicate of this GameObject and break the prefab to remove ragdoll components. It is recommended to set up the ragdoll on a new model instance, add PuppetMater to that and assign the original character as it's 'Animated Target'.", miniLabelStyle);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fix muscle positions and rotations to their targets
|
||||
if (!Application.isPlaying && script.targetRoot != null)
|
||||
{
|
||||
if (script.transform.parent == script.targetRoot.parent)
|
||||
{
|
||||
script.transform.localPosition = Vector3.zero;
|
||||
script.transform.localRotation = Quaternion.identity;
|
||||
script.targetRoot.localPosition = Vector3.zero;
|
||||
script.targetRoot.localRotation = Quaternion.identity;
|
||||
}
|
||||
|
||||
foreach (Muscle m in script.muscles)
|
||||
{
|
||||
if (m.joint != null && m.target != null && !m.isPropMuscle)
|
||||
{
|
||||
m.joint.transform.position = m.target.position;
|
||||
|
||||
var r = m.joint.GetComponent<Rigidbody>();
|
||||
|
||||
bool hasChild = false;
|
||||
foreach (Muscle otherMuscle in script.muscles)
|
||||
{
|
||||
if (otherMuscle != m && otherMuscle.joint != null && otherMuscle.joint.connectedBody != null)
|
||||
{
|
||||
if (otherMuscle.joint.connectedBody == r)
|
||||
{
|
||||
hasChild = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasChild)
|
||||
{
|
||||
m.joint.transform.rotation = m.target.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Application.isPlaying && script.humanoidConfig != null && script.targetRoot != null && script.targetAnimator != null && script.targetAnimator.isHuman)
|
||||
{
|
||||
script.humanoidConfig.ApplyTo(script);
|
||||
|
||||
GUILayout.Space(5);
|
||||
EditorGUILayout.BeginVertical("Box");
|
||||
GUILayout.Label("Properties of this PuppetMaster are locked to the Humanoid Config file.", miniLabelStyle);
|
||||
EditorGUILayout.EndVertical();
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
|
||||
script.muscleSpring = Mathf.Clamp(script.muscleSpring, 0f, script.muscleSpring);
|
||||
script.muscleDamper = Mathf.Clamp(script.muscleDamper, 0f, script.muscleDamper);
|
||||
|
||||
DrawDefaultInspector();
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
// Prop Muscles
|
||||
GUIStyle largeHeaderStyle = new GUIStyle(GUI.skin.label);
|
||||
largeHeaderStyle.fontSize = 16;
|
||||
largeHeaderStyle.fontStyle = FontStyle.Normal;
|
||||
//largeHeaderStyle.alignment = TextAnchor.LowerLeft;
|
||||
largeHeaderStyle.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
GUI.color = Color.grey * 0.7f;
|
||||
|
||||
EditorGUILayout.LabelField("Prop Muscles", largeHeaderStyle);
|
||||
|
||||
GUI.color = Color.white;
|
||||
|
||||
EditorGUILayout.BeginHorizontal("Box");
|
||||
|
||||
addPropMuscleTo = (ConfigurableJoint)EditorGUILayout.ObjectField(GUIContent.none, addPropMuscleTo, typeof(ConfigurableJoint), true);
|
||||
|
||||
if (GUILayout.Button("Add Prop Muscle"))
|
||||
{
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
AddPropMuscle(script, addPropMuscleTo, addPropMuscleTo.transform.position, addPropMuscleTo.transform.rotation, Vector3.forward * 0.5f);
|
||||
serializedObject.Update();
|
||||
}
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
EditorGUILayout.LabelField("Prop Muscles are muscles that props (PuppetMasterProp) can be attached to. For example, to put a sword in the right hand, assign the right hand muscle above, click on 'Add Prop Muscle', then move the added muscle to where you would want the sword to be held. To pick up the sword, just assign it as 'Current Prop' in the PropMuscle component on the prop muscle.", miniLabelStyle);
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
|
||||
bool animatorWarning = targetAnimator != null && targetAnimator.cullingMode != AnimatorCullingMode.AlwaysAnimate;
|
||||
if (animatorWarning)
|
||||
{
|
||||
GUILayout.Space(5);
|
||||
EditorGUILayout.BeginVertical("Box");
|
||||
GUILayout.Label("If Animator 'Culling Mode' is not set to 'Always Animate', animation might pause and cause glitches when the ragdoll is out of SkinnedMeshRenderer.bounds.", miniLabelStyle);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
bool rendererWarning = false;
|
||||
foreach (SkinnedMeshRenderer s in skinnedMeshRenderers)
|
||||
{
|
||||
if (!s.updateWhenOffscreen)
|
||||
{
|
||||
rendererWarning = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (rendererWarning)
|
||||
{
|
||||
if (!animatorWarning) GUILayout.Space(5);
|
||||
EditorGUILayout.BeginVertical("Box");
|
||||
GUILayout.Label("One of the SkinnedMeshRenderers in the animated character hierarchy has 'Update When Offscreen' set to false. This might cause it to disappear from view when the ragdoll is out of the renderer's bounds.", miniLabelStyle);
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
public static void DestroyImmediateSafe(GameObject go)
|
||||
{
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
if (PrefabUtility.IsPartOfPrefabInstance(go)) PrefabUtility.UnpackPrefabInstance(PrefabUtility.GetNearestPrefabInstanceRoot(go), PrefabUnpackMode.Completely, InteractionMode.UserAction);
|
||||
#endif
|
||||
DestroyImmediate(go);
|
||||
}
|
||||
|
||||
public static bool AddPropMuscle(PuppetMaster script, ConfigurableJoint addPropMuscleTo, Vector3 position, Quaternion rotation, Vector3 additionalPinOffset, Transform targetParent = null)
|
||||
{
|
||||
var serializedObject = new SerializedObject(script);
|
||||
|
||||
if (addPropMuscleTo != null)
|
||||
{
|
||||
bool isFlat = script.HierarchyIsFlat();
|
||||
|
||||
var addToMuscle = script.GetMuscle(addPropMuscleTo);
|
||||
if (addToMuscle != null)
|
||||
{
|
||||
|
||||
GameObject go = new GameObject("Prop Muscle " + addPropMuscleTo.name);
|
||||
go.layer = addPropMuscleTo.gameObject.layer;
|
||||
go.transform.parent = isFlat ? script.transform : addPropMuscleTo.transform;
|
||||
go.transform.position = position;
|
||||
go.transform.rotation = rotation;
|
||||
|
||||
var r = go.AddComponent<Rigidbody>();
|
||||
|
||||
GameObject target = new GameObject("Prop Muscle Target " + addPropMuscleTo.name);
|
||||
target.gameObject.layer = addToMuscle.target.gameObject.layer;
|
||||
target.transform.parent = targetParent == null ? addToMuscle.target : targetParent;
|
||||
target.transform.position = go.transform.position;
|
||||
target.transform.rotation = go.transform.rotation;
|
||||
|
||||
var joint = go.AddComponent<ConfigurableJoint>();
|
||||
joint.connectedBody = addToMuscle.joint.gameObject.GetComponent<Rigidbody>();
|
||||
joint.xMotion = ConfigurableJointMotion.Locked;
|
||||
joint.yMotion = ConfigurableJointMotion.Locked;
|
||||
joint.zMotion = ConfigurableJointMotion.Locked;
|
||||
joint.angularXMotion = ConfigurableJointMotion.Locked;
|
||||
joint.angularYMotion = ConfigurableJointMotion.Locked;
|
||||
joint.angularZMotion = ConfigurableJointMotion.Locked;
|
||||
|
||||
r.interpolation = joint.connectedBody.interpolation;
|
||||
|
||||
int newLength = script.muscles.Length + 1;
|
||||
int newMuscleIndex = newLength - 1;
|
||||
|
||||
SerializedProperty muscles = serializedObject.FindProperty("muscles");
|
||||
muscles.InsertArrayElementAtIndex(newMuscleIndex);
|
||||
|
||||
muscles.GetArrayElementAtIndex(newMuscleIndex).FindPropertyRelative("index").intValue = newMuscleIndex;
|
||||
muscles.GetArrayElementAtIndex(newMuscleIndex).FindPropertyRelative("joint").objectReferenceValue = joint;
|
||||
muscles.GetArrayElementAtIndex(newMuscleIndex).FindPropertyRelative("target").objectReferenceValue = target.transform;
|
||||
muscles.GetArrayElementAtIndex(newMuscleIndex).FindPropertyRelative("props").FindPropertyRelative("group").intValue = 8;
|
||||
muscles.GetArrayElementAtIndex(newMuscleIndex).FindPropertyRelative("isPropMuscle").boolValue = true;
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
|
||||
var propMuscle = joint.gameObject.AddComponent<PropMuscle>();
|
||||
var propMuscleS = new SerializedObject(propMuscle);
|
||||
propMuscleS.FindProperty("puppetMaster").objectReferenceValue = script;
|
||||
propMuscleS.FindProperty("additionalPinOffset").vector3Value = additionalPinOffset;
|
||||
|
||||
propMuscleS.ApplyModifiedProperties();
|
||||
|
||||
if (additionalPinOffset != Vector3.zero)
|
||||
{
|
||||
propMuscleS.Update();
|
||||
ConfigurableJoint additionalJoint = null;
|
||||
Transform additionalTarget = null;
|
||||
PropMuscleInspector.AddAdditionalPinUnserialized(propMuscle, out additionalJoint, out additionalTarget);
|
||||
|
||||
muscles.GetArrayElementAtIndex(newMuscleIndex).FindPropertyRelative("additionalPin").objectReferenceValue = additionalJoint;
|
||||
muscles.GetArrayElementAtIndex(newMuscleIndex).FindPropertyRelative("additionalPinTarget").objectReferenceValue = additionalTarget;
|
||||
|
||||
propMuscleS.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
// TODO Do not add if addPropMuscleTo already has a PropMuscle
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Can't add Prop Muscle to a ConfigurableJoint that is not in the list of PuppetMaster.muscles.", script.transform);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Please assign the ConfigurableJoint of the muscle you wish to add the Prop Muscle to.", script.transform);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsRagdoll()
|
||||
{
|
||||
Rigidbody[] rigidbodies = (Rigidbody[])script.gameObject.GetComponentsInChildren<Rigidbody>();
|
||||
foreach (Rigidbody r in rigidbodies)
|
||||
{
|
||||
if (r != script.GetComponent<Rigidbody>()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion Inspector
|
||||
|
||||
#region Scene
|
||||
|
||||
|
||||
void OnSceneGUI()
|
||||
{
|
||||
if (script == null) return;
|
||||
if (Application.isPlaying) return;
|
||||
|
||||
foreach (Muscle m in script.muscles)
|
||||
{
|
||||
if (m.joint != null)
|
||||
{
|
||||
var propMuscle = m.joint.GetComponent<PropMuscle>();
|
||||
if (propMuscle != null)
|
||||
{
|
||||
PropMuscleInspector.DrawScene(propMuscle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Handles.color = Color.white;
|
||||
}
|
||||
|
||||
|
||||
#endregion Scene
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d63c5bd59fb5541eeb1098d1f4a6d01c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,37 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
|
||||
namespace RootMotion.Dynamics
|
||||
{
|
||||
[CustomEditor(typeof(PuppetMasterProp))]
|
||||
public class PuppetMasterPropInspector : Editor
|
||||
{
|
||||
|
||||
private PuppetMasterProp script { get { return target as PuppetMasterProp; } }
|
||||
|
||||
private GUIStyle style = new GUIStyle();
|
||||
private GUIStyle miniLabelStyle = new GUIStyle();
|
||||
private static Color pro = new Color(0.5f, 0.7f, 0.3f, 1f);
|
||||
private static Color free = new Color(0.2f, 0.3f, 0.1f, 1f);
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (script == null) return;
|
||||
serializedObject.Update();
|
||||
|
||||
style.wordWrap = true;
|
||||
style.normal.textColor = EditorGUIUtility.isProSkin ? pro : free;
|
||||
|
||||
miniLabelStyle.wordWrap = true;
|
||||
miniLabelStyle.fontSize = 10;
|
||||
miniLabelStyle.normal.textColor = EditorStyles.miniLabel.normal.textColor;
|
||||
|
||||
DrawDefaultInspector();
|
||||
|
||||
script.muscleProps.group = Muscle.Group.Prop;
|
||||
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7f9c20921f4b1942b1f7b410dd66836
|
||||
timeCreated: 1550561801
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,46 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace RootMotion.Dynamics {
|
||||
|
||||
[CustomPropertyDrawer (typeof (Weight))]
|
||||
public class WeightDrawer : PropertyDrawer {
|
||||
|
||||
const int valueWidth = 30;
|
||||
const int gap = 20;
|
||||
const float min = 0;
|
||||
const float max = 1;
|
||||
|
||||
public override void OnGUI (Rect pos, SerializedProperty prop, GUIContent label) {
|
||||
|
||||
SerializedProperty mode = prop.FindPropertyRelative("mode");
|
||||
SerializedProperty floatValue = prop.FindPropertyRelative ("floatValue");
|
||||
SerializedProperty curve = prop.FindPropertyRelative ("curve");
|
||||
SerializedProperty tooltip = prop.FindPropertyRelative("tooltip");
|
||||
label.tooltip = tooltip.stringValue;
|
||||
|
||||
Rect left = new Rect(pos.x, pos.y, pos.width - valueWidth - gap, pos.height);
|
||||
Rect right = new Rect (pos.width - valueWidth, pos.y, valueWidth, pos.height);
|
||||
|
||||
Weight.Mode m = (Weight.Mode)mode.enumValueIndex;
|
||||
|
||||
if (m == Weight.Mode.Float) {
|
||||
floatValue.floatValue = EditorGUI.FloatField(left, label, floatValue.floatValue);
|
||||
} else {
|
||||
// Draw curve
|
||||
int indent = EditorGUI.indentLevel;
|
||||
|
||||
EditorGUI.PropertyField (left, curve, label);
|
||||
EditorGUI.indentLevel = indent;
|
||||
}
|
||||
|
||||
int i = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 0;
|
||||
|
||||
m = (Weight.Mode)EditorGUI.EnumPopup(right, GUIContent.none, ( Weight.Mode)mode.enumValueIndex, EditorStyles.miniBoldLabel);
|
||||
mode.enumValueIndex = (int)m;
|
||||
|
||||
EditorGUI.indentLevel = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f882d03cc62ed40c7b8febce6ef4a0de
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98d02ab32f6fa4551b1983c76cea4586
|
||||
folderAsset: yes
|
||||
timeCreated: 1452250914
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
using RootMotion.Dynamics;
|
||||
|
||||
namespace RootMotion.Demos {
|
||||
|
||||
[CustomEditor(typeof(CharacterMeleeDemo))]
|
||||
public class CharacterMeleeDemoInspector : Editor {
|
||||
|
||||
private CharacterMeleeDemo script { get { return target as CharacterMeleeDemo; }}
|
||||
|
||||
private GameObject replace;
|
||||
|
||||
private static Color pro = new Color(0.7f, 0.9f, 0.5f, 1f);
|
||||
private static Color free = new Color(0.4f, 0.5f, 0.3f, 1f);
|
||||
|
||||
public override void OnInspectorGUI() {
|
||||
GUI.changed = false;
|
||||
|
||||
if (!Application.isPlaying) {
|
||||
GUI.color = EditorGUIUtility.isProSkin? pro: free;
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
replace = (GameObject)EditorGUILayout.ObjectField("Replace Character Model", replace, typeof(GameObject), true);
|
||||
|
||||
if (replace != null) {
|
||||
if (GUILayout.Button("Replace")) {
|
||||
Debug.Log("PropMuscle needs to be set up manually and assigned as 'Prop Muscle' in the CharacterMeleeDemo component on the character.");
|
||||
|
||||
/* TODO Update to PropMuscle
|
||||
PropRoot propRoot = script.propRoot;
|
||||
Vector3 localPosition = propRoot.transform.localPosition;
|
||||
Quaternion localRotation = propRoot.transform.localRotation;
|
||||
propRoot.transform.parent = null;
|
||||
|
||||
CharacterPuppetInspector.ReplacePuppetModel(script as CharacterThirdPerson, replace);
|
||||
|
||||
Animator animator = script.characterAnimation.GetComponent<Animator>();
|
||||
PuppetMaster puppetMaster = script.transform.parent.GetComponentInChildren<PuppetMaster>();
|
||||
|
||||
propRoot.transform.parent = animator.GetBoneTransform(HumanBodyBones.RightHand);
|
||||
propRoot.transform.localPosition = localPosition;
|
||||
propRoot.transform.localRotation = localRotation;
|
||||
propRoot.puppetMaster = puppetMaster;
|
||||
propRoot.connectTo = GetRigidbody(puppetMaster, animator.GetBoneTransform(HumanBodyBones.RightLowerArm));
|
||||
|
||||
Debug.Log("You probably need to adjust the localPosition and localRotation of the Prop Root to match this character's hand.");
|
||||
*/
|
||||
UserControlAI[] userControls = (UserControlAI[])GameObject.FindObjectsOfType<UserControlAI>();
|
||||
foreach (UserControlAI ai in userControls) {
|
||||
if (ai.moveTarget == null) {
|
||||
ai.moveTarget = script.transform.parent.GetComponentInChildren<PuppetMaster>().muscles[0].joint.transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
DrawDefaultInspector();
|
||||
|
||||
if (GUI.changed) EditorUtility.SetDirty(script);
|
||||
}
|
||||
|
||||
private Rigidbody GetRigidbody(PuppetMaster puppetMaster, Transform target) {
|
||||
foreach (Muscle m in puppetMaster.muscles) {
|
||||
if (m.target == target) return m.joint.GetComponent<Rigidbody>();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c5c74fcc11b441abb014b8119cb9e5f
|
||||
timeCreated: 1452255675
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,176 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
using RootMotion.Dynamics;
|
||||
|
||||
namespace RootMotion.Demos {
|
||||
|
||||
[CustomEditor(typeof(CharacterPuppet))]
|
||||
public class CharacterPuppetInspector : Editor {
|
||||
|
||||
private CharacterPuppet script { get { return target as CharacterPuppet; }}
|
||||
|
||||
private GameObject replace;
|
||||
|
||||
private static Color pro = new Color(0.7f, 0.9f, 0.5f, 1f);
|
||||
private static Color free = new Color(0.4f, 0.5f, 0.3f, 1f);
|
||||
|
||||
public override void OnInspectorGUI() {
|
||||
GUI.changed = false;
|
||||
|
||||
if (!Application.isPlaying) {
|
||||
GUI.color = EditorGUIUtility.isProSkin? pro: free;
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
|
||||
replace = (GameObject)EditorGUILayout.ObjectField("Replace Character Model", replace, typeof(GameObject), true);
|
||||
|
||||
if (replace != null) {
|
||||
if (GUILayout.Button("Replace")) {
|
||||
ReplacePuppetModel(script as CharacterThirdPerson, replace);
|
||||
replace = null;
|
||||
}
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
DrawDefaultInspector();
|
||||
if (GUI.changed) EditorUtility.SetDirty(script);
|
||||
}
|
||||
|
||||
public static void ReplacePuppetModel(CharacterThirdPerson script, GameObject replace) {
|
||||
// Drag your Humanoid character to the scene, parent it to the "Animation Controller" just where the Pilot is
|
||||
GameObject find = GameObject.Find(replace.name);
|
||||
bool isSceneObject = find != null && find == replace;
|
||||
|
||||
GameObject instance = isSceneObject? replace: (GameObject)GameObject.Instantiate(replace, script.transform.position, script.transform.rotation);
|
||||
instance.transform.parent = script.characterAnimation.transform;
|
||||
instance.name = replace.name;
|
||||
instance.transform.position = script.transform.position;
|
||||
instance.transform.rotation = script.transform.rotation;
|
||||
|
||||
Animator animator = instance.GetComponentInChildren<Animator>();
|
||||
bool isAnimator = animator != null;
|
||||
if (!isAnimator || !animator.isHuman) {
|
||||
Debug.LogWarning("The demo character controller requires a Humanoid character.");
|
||||
return;
|
||||
}
|
||||
|
||||
SkinnedMeshRenderer[] skinnedMeshRenders = instance.GetComponentsInChildren<SkinnedMeshRenderer>();
|
||||
foreach (SkinnedMeshRenderer s in skinnedMeshRenders) s.updateWhenOffscreen = true;
|
||||
|
||||
// Unparent "Character Controller" from the "ThirdPersonPuppet"
|
||||
Transform oldRoot = script.transform.parent;
|
||||
PuppetMaster oldPM = script.transform.parent.GetComponentInChildren<PuppetMaster>();
|
||||
RigidbodyInterpolation oldInterpolation = oldPM.muscles[0].joint.GetComponent<Rigidbody>().interpolation;
|
||||
BehaviourBase[] behaviours = script.transform.parent.GetComponentsInChildren<BehaviourBase>();
|
||||
Camera camera = script.transform.parent.GetComponentInChildren<Camera>();
|
||||
CameraController cameraController = null;
|
||||
if (camera != null) cameraController = camera.gameObject.GetComponent<CameraController>();
|
||||
LayerSetup layerSetup = oldPM.GetComponent<LayerSetup>();
|
||||
|
||||
script.transform.parent = null;
|
||||
|
||||
// Delete the "Pilot"
|
||||
DestroyImmediate(script.characterAnimation.transform.GetChild(0).gameObject);
|
||||
|
||||
// Replace the Avatar in the Animator component of the "Animation Controller" gameobject.
|
||||
Animator characterAnimator = script.characterAnimation.GetComponent<Animator>();
|
||||
characterAnimator.avatar = animator.avatar;
|
||||
|
||||
// Add BipedRagdollCreator to the "Animation Controller", use it to create a ragdoll, delete the component when done
|
||||
Joint joint = instance.GetComponentInChildren<Joint>();
|
||||
bool isRagdoll = joint != null;
|
||||
bool isConfigurable = isRagdoll && joint is ConfigurableJoint;
|
||||
|
||||
if (isRagdoll && !isConfigurable) {
|
||||
JointConverter.ToConfigurable(instance);
|
||||
}
|
||||
|
||||
if (!isRagdoll) {
|
||||
BipedRagdollReferences r = BipedRagdollReferences.FromAvatar(animator);
|
||||
BipedRagdollCreator.Options options = BipedRagdollCreator.AutodetectOptions(r);
|
||||
|
||||
// Edit options here if you need to
|
||||
|
||||
BipedRagdollCreator.Create(r, options);
|
||||
}
|
||||
|
||||
// Delete the Animator from your character root (not the one on "Animation Controller")
|
||||
DestroyImmediate(animator);
|
||||
|
||||
// Add PuppetMaster to the "Character Controller", click on Set Up PuppetMaster.
|
||||
int cLayer = layerSetup != null? layerSetup.characterControllerLayer: script.gameObject.layer;
|
||||
int rLayer = layerSetup != null? layerSetup.ragdollLayer: oldPM.muscles[0].joint.gameObject.layer;
|
||||
|
||||
PuppetMaster newPuppetMaster = PuppetMaster.SetUp(script.transform, cLayer, rLayer);
|
||||
foreach (Muscle m in newPuppetMaster.muscles) {
|
||||
m.joint.GetComponent<Rigidbody>().interpolation = oldInterpolation;
|
||||
}
|
||||
|
||||
// Move the "Puppet" and "Fall" behaviours from the old rig to the new one (parent them to the "Behaviours").
|
||||
Transform behaviourRoot = newPuppetMaster.transform.parent.Find("Behaviours");
|
||||
Transform oldBehaviourRoot = behaviours.Length > 0? behaviours[0].transform.parent: FindChildNameContaining(oldRoot, "Behaviours");
|
||||
foreach (BehaviourBase behaviour in behaviours) behaviour.transform.parent = behaviourRoot;
|
||||
if (oldBehaviourRoot != null) DestroyImmediate(oldBehaviourRoot.gameObject);
|
||||
|
||||
// 9. Move the "Character Camera" to the new rig, replace the "Target" of the CharacterController component
|
||||
if (camera != null) camera.transform.parent = newPuppetMaster.transform.parent;
|
||||
if (cameraController != null) cameraController.target = newPuppetMaster.muscles[0].joint.transform;
|
||||
|
||||
// THIS IS NOT REQUIRED SINCE 0.3 Use the RagdollEditor (add it to the PuppetMaster) to multiply the mass of the ragdoll by 10
|
||||
|
||||
// 10. Copy the LayerSetup component from the old PuppetMaster to the new one if you have not set up the layers in the Layer Collision Matrix.
|
||||
if (layerSetup != null) {
|
||||
LayerSetup newLayerSetup = newPuppetMaster.gameObject.AddComponent<LayerSetup>();
|
||||
if (newLayerSetup != null) {
|
||||
newLayerSetup.characterController = layerSetup.characterController;
|
||||
newLayerSetup.characterControllerLayer = layerSetup.characterControllerLayer;
|
||||
newLayerSetup.ragdollLayer = layerSetup.ragdollLayer;
|
||||
newLayerSetup.ignoreCollisionWithCharacterController = layerSetup.ignoreCollisionWithCharacterController;
|
||||
}
|
||||
}
|
||||
|
||||
// 11. Make sure the new PuppetMaster settings match with the old one, delete the old rig
|
||||
newPuppetMaster.state = oldPM.state;
|
||||
newPuppetMaster.stateSettings = oldPM.stateSettings;
|
||||
newPuppetMaster.mode = oldPM.mode;
|
||||
newPuppetMaster.blendTime = oldPM.blendTime;
|
||||
newPuppetMaster.fixTargetTransforms = oldPM.fixTargetTransforms;
|
||||
newPuppetMaster.solverIterationCount = oldPM.solverIterationCount;
|
||||
newPuppetMaster.visualizeTargetPose = oldPM.visualizeTargetPose;
|
||||
newPuppetMaster.mappingWeight = oldPM.mappingWeight;
|
||||
newPuppetMaster.pinWeight = oldPM.pinWeight;
|
||||
newPuppetMaster.muscleWeight = oldPM.muscleWeight;
|
||||
newPuppetMaster.muscleSpring = oldPM.muscleSpring;
|
||||
newPuppetMaster.muscleDamper = oldPM.muscleDamper;
|
||||
newPuppetMaster.pinPow = oldPM.pinPow;
|
||||
newPuppetMaster.pinDistanceFalloff = oldPM.pinDistanceFalloff;
|
||||
newPuppetMaster.updateJointAnchors = oldPM.updateJointAnchors;
|
||||
newPuppetMaster.supportTranslationAnimation = oldPM.supportTranslationAnimation;
|
||||
newPuppetMaster.angularLimits = oldPM.angularLimits;
|
||||
newPuppetMaster.internalCollisions = oldPM.internalCollisions;
|
||||
|
||||
// 12 Parent this CharacterPuppet to the new puppet root
|
||||
script.transform.parent = newPuppetMaster.transform.parent;
|
||||
|
||||
// 13. Delete the old root
|
||||
script.transform.parent.name = oldRoot.name;
|
||||
|
||||
for (int i = 0; i < oldRoot.childCount; i++) {
|
||||
var child = oldRoot.GetChild(i);
|
||||
if (child != oldPM.transform && child != behaviourRoot) {
|
||||
child.parent = script.transform.parent;
|
||||
}
|
||||
}
|
||||
DestroyImmediate(oldRoot.gameObject);
|
||||
}
|
||||
|
||||
private static Transform FindChildNameContaining(Transform t, string name) {
|
||||
for (int i = 0; i < t.childCount; i++) {
|
||||
if (t.GetChild(i).name.Contains(name)) return t.GetChild(i);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d86142a0934834027a39b8a2e105c009
|
||||
timeCreated: 1452250930
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e216fee95a1f04dd38a1e457aed7617a
|
||||
folderAsset: yes
|
||||
timeCreated: 1436186405
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,168 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
using RootMotion;
|
||||
|
||||
namespace RootMotion.Dynamics {
|
||||
|
||||
[CustomEditor(typeof(BipedRagdollCreator))]
|
||||
public class BipedRagdollCreatorInspector : Editor {
|
||||
|
||||
public BipedRagdollCreator script { get { return target as BipedRagdollCreator; }}
|
||||
|
||||
private GUIStyle miniLabelStyle = new GUIStyle();
|
||||
|
||||
void OnEnable() {
|
||||
if (script == null) return;
|
||||
//if (Application.isPlaying) return;
|
||||
|
||||
// Autodetection
|
||||
if (script.references.IsEmpty(false)) {
|
||||
Animator animator = script.gameObject.GetComponent<Animator>();
|
||||
|
||||
if (animator == null && script.references.root != null) {
|
||||
animator = script.references.root.GetComponentInChildren<Animator>();
|
||||
if (animator == null) animator = GetAnimatorInParents(script.references.root);
|
||||
}
|
||||
|
||||
if (animator != null) {
|
||||
script.references = BipedRagdollReferences.FromAvatar(animator);
|
||||
|
||||
} else {
|
||||
|
||||
BipedReferences r = new BipedReferences();
|
||||
BipedReferences.AutoDetectReferences(ref r, script.transform, BipedReferences.AutoDetectParams.Default);
|
||||
if (r.isFilled) script.references = BipedRagdollReferences.FromBipedReferences(r);
|
||||
}
|
||||
|
||||
if (!OnRoot()) {
|
||||
Debug.LogWarning("BipedRagdollCreator must be added to the root of the character. Destroying the component.");
|
||||
DestroyImmediate(script);
|
||||
return;
|
||||
}
|
||||
|
||||
string msg = string.Empty;
|
||||
if (script.references.IsValid(ref msg)) {
|
||||
script.options = BipedRagdollCreator.AutodetectOptions(script.references);
|
||||
//BipedRagdollCreator.Create(script.references, script.options);
|
||||
|
||||
//if (animator != null) DestroyImmediate(animator);
|
||||
//if (script.GetComponent<Animation>() != null) DestroyImmediate(script.GetComponent<Animation>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool OnRoot() {
|
||||
if (script.references.Contains(script.transform, true)) return false;
|
||||
if (script.references.root != null) {
|
||||
var bipedRagdollCreatorOnChild = script.references.root.GetComponentInChildren<BipedRagdollCreator>();
|
||||
if (bipedRagdollCreatorOnChild != null && bipedRagdollCreatorOnChild.transform != script.references.root) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI() {
|
||||
miniLabelStyle.wordWrap = true;
|
||||
miniLabelStyle.fontSize = 9;
|
||||
miniLabelStyle.normal.textColor = EditorStyles.miniLabel.normal.textColor;
|
||||
|
||||
serializedObject.Update();
|
||||
|
||||
//if (Application.isPlaying) {
|
||||
// GUILayout.BeginVertical("Box");
|
||||
// GUILayout.Label("Can not edit ragdolls in play mode.");
|
||||
// GUILayout.EndVertical();
|
||||
// return;
|
||||
//}
|
||||
|
||||
GUI.changed = false;
|
||||
GUILayout.Space(10);
|
||||
EditorGUILayout.BeginVertical("Box");
|
||||
int indent = EditorGUI.indentLevel;
|
||||
EditorGUI.indentLevel = 1;
|
||||
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("references"), new GUIContent("References", "Definition of the biped ragdoll."), true);
|
||||
|
||||
if (GUI.changed) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorUtility.SetDirty(script);
|
||||
}
|
||||
|
||||
string msg = string.Empty;
|
||||
bool referencesValid = script.references.IsValid(ref msg);
|
||||
if (serializedObject.FindProperty("references").isExpanded) GUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
if (referencesValid) {
|
||||
|
||||
if (!script.canBuild) {
|
||||
UnityEditor.EditorApplication.isPaused = true;
|
||||
GUILayout.Space(5);
|
||||
|
||||
if (GUILayout.Button("Create a Ragdoll")) {
|
||||
|
||||
script.canBuild = true;
|
||||
}
|
||||
GUILayout.Label("Clears all existing physics components, creates a new ragdoll and starts live-updating. NB! THIS CAN NOT BE UNDONE!", miniLabelStyle);
|
||||
GUILayout.Space(5);
|
||||
}
|
||||
|
||||
if (script.canBuild) {
|
||||
GUI.changed = false;
|
||||
//GUILayout.Space(5);
|
||||
EditorGUILayout.BeginVertical("Box");
|
||||
EditorGUILayout.PropertyField(serializedObject.FindProperty("options"), new GUIContent("Options", "Options for automatic ragdoll generation."), true);
|
||||
|
||||
if (GUI.changed) {
|
||||
serializedObject.ApplyModifiedProperties();
|
||||
EditorUtility.SetDirty(script);
|
||||
}
|
||||
if (serializedObject.FindProperty("options").isExpanded) GUILayout.Space(5);
|
||||
EditorGUILayout.EndVertical();
|
||||
|
||||
BipedRagdollCreator.Create(script.references, script.options);
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (GUILayout.Button("Done")) {
|
||||
UnityEditor.EditorApplication.isPaused = false;
|
||||
EditorGUI.indentLevel = indent;
|
||||
DestroyImmediate(script);
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.Label("Removes this component.", miniLabelStyle);
|
||||
|
||||
if (GUILayout.Button("Start Editing Manually")) {
|
||||
script.gameObject.AddComponent<RagdollEditor>();
|
||||
EditorGUI.indentLevel = indent;
|
||||
DestroyImmediate(script);
|
||||
return;
|
||||
}
|
||||
|
||||
GUILayout.Label("Replaces this component with the RagdollEditor.", miniLabelStyle);
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
}
|
||||
} else {
|
||||
GUILayout.Space(10);
|
||||
GUILayout.BeginVertical("Box");
|
||||
GUILayout.Label(msg);
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.Space(10);
|
||||
|
||||
if (script.canBuild) RagdollCreator.ClearAll(script.transform);
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel = indent;
|
||||
}
|
||||
|
||||
private static Animator GetAnimatorInParents(Transform transform) {
|
||||
if (transform.GetComponent<Animator>() != null) return transform.GetComponent<Animator>();
|
||||
if (transform.parent == null) return null;
|
||||
return GetAnimatorInParents(transform.parent);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3b4d10c84c3764847812025f2e5986e6
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,48 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
namespace RootMotion.Dynamics {
|
||||
|
||||
[CustomEditor(typeof(CharacterJoint))]
|
||||
public class CharacterJointInspector : JointInspector {
|
||||
|
||||
private static Color
|
||||
highTwistColor = new Color(1, 0.75f, 0, 0.3f),
|
||||
lowTwistColor = new Color(0.8f, 0.65f, 0, 0.3f),
|
||||
swing1Color = new Color(0, 1, 0, 0.3f),
|
||||
swing2Color = new Color(0, 0, 1, 0.3f);
|
||||
|
||||
public static void DrawJoint(CharacterJoint joint, bool modifiable = true, float alphaMlp = 1f) {
|
||||
if (joint == null) return;
|
||||
|
||||
Vector3 axis = Vector3.zero;
|
||||
if (joint.axis != Vector3.zero) axis = joint.axis.normalized;
|
||||
Vector3 swingAxis = Vector3.zero;
|
||||
if (joint.swingAxis != Vector3.zero) swingAxis = joint.swingAxis.normalized;
|
||||
Vector3 crossAxis = swingAxis;
|
||||
if (swingAxis != axis) crossAxis = Vector3.Cross(swingAxis, axis);
|
||||
|
||||
float newLowTwistLimit = DrawJointLimit(joint as Joint, "Low Twist Limit", axis, joint.lowTwistLimit.limit, MlpAlpha(lowTwistColor, alphaMlp), -25, modifiable);
|
||||
joint.lowTwistLimit = NewJointLimit(newLowTwistLimit, joint.lowTwistLimit, -180, 180);
|
||||
|
||||
float newHighTwistLimit = joint.highTwistLimit.limit;
|
||||
if (newLowTwistLimit != 0 || (newLowTwistLimit == 0 && newHighTwistLimit != 0)) newHighTwistLimit = DrawJointLimit(joint as Joint, "High Twist Limit", axis, joint.highTwistLimit.limit, MlpAlpha(highTwistColor, alphaMlp), 25, modifiable);
|
||||
joint.highTwistLimit = NewJointLimit(newHighTwistLimit, joint.highTwistLimit, -180, 180);
|
||||
|
||||
float newSwing1Limit = DrawJointLimit(joint as Joint, "Swing1 Limit", swingAxis, -joint.swing1Limit.limit, MlpAlpha(swing1Color, alphaMlp), 25, false);
|
||||
newSwing1Limit = DrawJointLimit(joint as Joint, "Swing1 Limit", swingAxis, -newSwing1Limit, MlpAlpha(swing1Color, alphaMlp), 25, modifiable);
|
||||
joint.swing1Limit = NewJointLimit(newSwing1Limit, joint.swing1Limit, 0, 180);
|
||||
|
||||
float newSwing2Limit = DrawJointLimit(joint as Joint, "Swing2 Limit", crossAxis, -joint.swing2Limit.limit, MlpAlpha(swing2Color, alphaMlp), 25, false);
|
||||
newSwing2Limit = DrawJointLimit(joint as Joint, "Swing2 Limit", crossAxis, -newSwing2Limit, MlpAlpha(swing2Color, alphaMlp), 25, modifiable);
|
||||
joint.swing2Limit = NewJointLimit(newSwing2Limit, joint.swing2Limit, 0, 180);
|
||||
}
|
||||
|
||||
public void OnSceneGUI() {
|
||||
DrawJoint(target as CharacterJoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df75c9b4717ec4cf996ae3055c26373e
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,250 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
|
||||
namespace RootMotion.Dynamics {
|
||||
|
||||
// Tools for editing Colliders.
|
||||
public class ColliderTools: Editor {
|
||||
|
||||
public static void ConvertToSphereCollider(CapsuleCollider capsule) {
|
||||
SphereCollider sphere = Undo.AddComponent(capsule.gameObject, typeof(SphereCollider)) as SphereCollider;
|
||||
|
||||
sphere.center = capsule.center;
|
||||
sphere.radius = capsule.radius;
|
||||
|
||||
Undo.DestroyObjectImmediate(capsule);
|
||||
}
|
||||
|
||||
public static void ConvertToSphereCollider(BoxCollider box) {
|
||||
SphereCollider sphere = Undo.AddComponent(box.gameObject, typeof(SphereCollider)) as SphereCollider;
|
||||
|
||||
sphere.center = box.center;
|
||||
sphere.radius = Mathf.Max(new float[3] { box.size.x, box.size.y, box.size.z }) * 0.5f;
|
||||
|
||||
Undo.DestroyObjectImmediate(box);
|
||||
}
|
||||
|
||||
public static void ConvertToBoxCollider(SphereCollider sphere) {
|
||||
BoxCollider box = Undo.AddComponent(sphere.gameObject, typeof(BoxCollider)) as BoxCollider;
|
||||
|
||||
box.center = sphere.center;
|
||||
box.size = Vector3.one * sphere.radius * 2f;
|
||||
|
||||
Undo.DestroyObjectImmediate(sphere);
|
||||
}
|
||||
|
||||
public static void ConvertToCapsuleCollider(SphereCollider sphere) {
|
||||
CapsuleCollider capsule = Undo.AddComponent(sphere.gameObject, typeof(CapsuleCollider)) as CapsuleCollider;
|
||||
|
||||
capsule.center = sphere.center;
|
||||
capsule.radius = sphere.radius;
|
||||
capsule.height = sphere.radius;
|
||||
|
||||
Undo.DestroyObjectImmediate(sphere);
|
||||
}
|
||||
|
||||
public static void ConvertToBoxCollider(CapsuleCollider capsule) {
|
||||
BoxCollider box = Undo.AddComponent(capsule.gameObject, typeof(BoxCollider)) as BoxCollider;
|
||||
|
||||
box.center = capsule.center;
|
||||
|
||||
float diameter = capsule.radius * 2f;
|
||||
float height = Mathf.Max(diameter, capsule.height);
|
||||
|
||||
if (capsule.direction == 0) box.size = new Vector3(height, diameter, diameter);
|
||||
else if (capsule.direction == 1) box.size = new Vector3(diameter, height, diameter);
|
||||
else if (capsule.direction == 2) box.size = new Vector3(diameter, diameter, height);
|
||||
|
||||
Undo.DestroyObjectImmediate(capsule);
|
||||
}
|
||||
|
||||
public static void ConvertToCapsuleCollider(BoxCollider box) {
|
||||
float absX = Mathf.Abs(box.size.x);
|
||||
float absY = Mathf.Abs(box.size.y);
|
||||
float absZ = Mathf.Abs(box.size.z);
|
||||
|
||||
CapsuleCollider capsule = Undo.AddComponent(box.gameObject, typeof(CapsuleCollider)) as CapsuleCollider;
|
||||
|
||||
capsule.center = box.center;
|
||||
|
||||
capsule.direction = 0;
|
||||
if (absY > absX && absY > absZ) capsule.direction = 1;
|
||||
if (absZ > absX && absZ > absY) capsule.direction = 2;
|
||||
|
||||
if (capsule.direction == 0) {
|
||||
capsule.height = absX;
|
||||
capsule.radius = Mathf.Max(absY, absZ) * 0.5f;
|
||||
} else if (capsule.direction == 1) {
|
||||
capsule.height = absY;
|
||||
capsule.radius = Mathf.Max(absX, absZ) * 0.5f;
|
||||
} else if (capsule.direction == 2) {
|
||||
capsule.height = absZ;
|
||||
capsule.radius = Mathf.Max(absX, absY) * 0.5f;
|
||||
}
|
||||
|
||||
Undo.DestroyObjectImmediate(box);
|
||||
}
|
||||
|
||||
public static void SetColliderCenterWorld(Collider r, Collider s, Vector3 value, Transform root) {
|
||||
Vector3 newCenter = r.transform.InverseTransformPoint(value);
|
||||
Vector3 centerWorld = Vector3.zero;
|
||||
Vector3 deltaWorld = Vector3.zero;
|
||||
|
||||
if (r is BoxCollider) {
|
||||
var box = r as BoxCollider;
|
||||
centerWorld = r.transform.TransformPoint(box.center);
|
||||
deltaWorld = value - centerWorld;
|
||||
box.center = newCenter;
|
||||
|
||||
if (s != null && s is BoxCollider) s.transform.GetComponent<BoxCollider>().center += s.transform.InverseTransformVector(SymmetryTools.Mirror(deltaWorld, root));
|
||||
return;
|
||||
}
|
||||
|
||||
if (r is CapsuleCollider) {
|
||||
var capsule = r as CapsuleCollider;
|
||||
centerWorld = r.transform.TransformPoint(capsule.center);
|
||||
deltaWorld = value - centerWorld;
|
||||
capsule.center = newCenter;
|
||||
|
||||
if (s != null && s is CapsuleCollider) s.transform.GetComponent<CapsuleCollider>().center += s.transform.InverseTransformVector(SymmetryTools.Mirror(deltaWorld, root));
|
||||
return;
|
||||
}
|
||||
|
||||
if (r is SphereCollider) {
|
||||
var sphere = r as SphereCollider;
|
||||
centerWorld = r.transform.TransformPoint(sphere.center);
|
||||
deltaWorld = value - centerWorld;
|
||||
sphere.center = newCenter;
|
||||
|
||||
if (s != null && s is SphereCollider) s.transform.GetComponent<SphereCollider>().center += s.transform.InverseTransformVector(SymmetryTools.Mirror(deltaWorld, root));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector3 GetColliderCenterWorld(Collider r) {
|
||||
if (r == null) return Vector3.zero;
|
||||
|
||||
if (r is BoxCollider) {
|
||||
return r.transform.TransformPoint((r as BoxCollider).center);
|
||||
}
|
||||
|
||||
if (r is CapsuleCollider) {
|
||||
return r.transform.TransformPoint((r as CapsuleCollider).center);
|
||||
}
|
||||
|
||||
if (r is SphereCollider) {
|
||||
return r.transform.TransformPoint((r as SphereCollider).center);
|
||||
}
|
||||
|
||||
return r.transform.position;
|
||||
}
|
||||
|
||||
public static void SetColliderSize(Collider r, Collider s, Vector3 value, Vector3 lastValue, Vector3 lastValueS, Transform root) {
|
||||
Vector3 delta = Vector3.zero;
|
||||
Vector3 deltaM = Vector3.zero;
|
||||
|
||||
if (r is BoxCollider) {
|
||||
var box = r as BoxCollider;
|
||||
delta = value - box.size;
|
||||
box.size = value;
|
||||
|
||||
if (s != null && s is BoxCollider) {
|
||||
var boxS = s as BoxCollider;
|
||||
|
||||
deltaM = SymmetryTools.MirrorDelta(r.transform, s.transform, root, delta);
|
||||
boxS.size += deltaM;
|
||||
}
|
||||
}
|
||||
|
||||
if (r is CapsuleCollider) {
|
||||
var capsule = r as CapsuleCollider;
|
||||
|
||||
delta = value - GetCapsuleSize(capsule);
|
||||
SetCapsuleSize(capsule, value, lastValue);
|
||||
|
||||
if (s != null && s is CapsuleCollider) {
|
||||
var capsuleS = s as CapsuleCollider;
|
||||
|
||||
if (capsuleS != null) {
|
||||
deltaM = SymmetryTools.MirrorDelta(r.transform, s.transform, root, delta);
|
||||
Vector3 capsuleSSize = GetCapsuleSize(capsuleS);
|
||||
capsuleSSize += deltaM;
|
||||
|
||||
SetCapsuleSize(capsuleS, capsuleSSize, lastValueS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (r is SphereCollider) {
|
||||
var sphere = r as SphereCollider;
|
||||
|
||||
delta = value - GetColliderSize(r);
|
||||
SetSphereSize(sphere, value, lastValue);
|
||||
|
||||
if (s != null && s is SphereCollider) {
|
||||
var sphereS = s as SphereCollider;
|
||||
Vector3 sphereSSize = GetColliderSize(s);
|
||||
sphereSSize += delta;
|
||||
|
||||
if (sphereS != null) {
|
||||
SetSphereSize(sphereS, sphereSSize, lastValueS);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector3 GetColliderSize(Collider r) {
|
||||
if (r == null) return Vector3.zero;
|
||||
|
||||
if (r is BoxCollider) return (r as BoxCollider).size;
|
||||
if (r is CapsuleCollider) return GetCapsuleSize(r as CapsuleCollider);
|
||||
if (r is SphereCollider) return Vector3.one * (r as SphereCollider).radius;
|
||||
|
||||
return r.transform.localScale;
|
||||
}
|
||||
|
||||
public static void SetCapsuleSize(CapsuleCollider capsule, Vector3 size, Vector3 lastSize) {
|
||||
if (capsule.direction == 0) {
|
||||
capsule.height = size.x;
|
||||
|
||||
float absY = Mathf.Abs(size.y - lastSize.y);
|
||||
float absZ = Mathf.Abs(size.z - lastSize.z);
|
||||
capsule.radius = absY > absZ? size.y: size.z;
|
||||
}
|
||||
|
||||
if (capsule.direction == 1) {
|
||||
capsule.height = size.y;
|
||||
|
||||
float absX = Mathf.Abs(size.x - lastSize.x);
|
||||
float absZ = Mathf.Abs(size.z - lastSize.z);
|
||||
capsule.radius = absX > absZ? size.x: size.z;
|
||||
}
|
||||
|
||||
if (capsule.direction == 2) {
|
||||
capsule.height = size.z;
|
||||
|
||||
float absX = Mathf.Abs(size.x - lastSize.x);
|
||||
float absY = Mathf.Abs(size.y - lastSize.y);
|
||||
capsule.radius = absX > absY? size.x: size.y;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void SetSphereSize(SphereCollider sphere, Vector3 size, Vector3 lastSize) {
|
||||
if (size.x != lastSize.z) {
|
||||
sphere.radius += size.x - lastSize.x;
|
||||
} else if (size.y != lastSize.y) {
|
||||
sphere.radius += size.y - lastSize.y;
|
||||
} else if (size.z != lastSize.z) {
|
||||
sphere.radius += size.z - lastSize.z;
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector3 GetCapsuleSize(CapsuleCollider capsule) {
|
||||
if (capsule.direction == 0) return new Vector3(capsule.height, capsule.radius, capsule.radius);
|
||||
if (capsule.direction == 1) return new Vector3(capsule.radius, capsule.height, capsule.radius);
|
||||
return new Vector3(capsule.radius, capsule.radius, capsule.height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9ed7bfe787e81435c8b76e75353a45fb
|
||||
timeCreated: 1444653054
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,56 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
|
||||
namespace RootMotion.Dynamics {
|
||||
|
||||
[CustomEditor(typeof(ConfigurableJoint))]
|
||||
public class ConfigurableJointInspector : JointInspector {
|
||||
|
||||
private static Color
|
||||
highAngularXColor = new Color(1, 0.75f, 0, 0.3f),
|
||||
lowAngularXColor = new Color(0.8f, 0.65f, 0, 0.3f),
|
||||
angularYColor = new Color(0, 1, 0, 0.3f),
|
||||
angularZColor = new Color(0, 0, 1, 0.3f);
|
||||
|
||||
private const string lowX = "Low X", highX = "High X", y = "Y", z = "Z";
|
||||
|
||||
public static void DrawJoint(ConfigurableJoint joint, bool modifiable = true, float alphaMlp = 1f) {
|
||||
if (joint == null) return;
|
||||
|
||||
Vector3 axis = Vector3.zero;
|
||||
if (joint.axis != Vector3.zero) axis = joint.axis.normalized;
|
||||
Vector3 secondaryAxis = Vector3.zero;
|
||||
if (joint.secondaryAxis != Vector3.zero) secondaryAxis = joint.secondaryAxis.normalized;
|
||||
Vector3 crossAxis = secondaryAxis;
|
||||
if (secondaryAxis != axis) crossAxis = Vector3.Cross(secondaryAxis, axis);
|
||||
|
||||
if (joint.angularXMotion == ConfigurableJointMotion.Limited) {
|
||||
float newLowAngularXLimit = DrawJointLimit(joint as Joint, lowX, axis, joint.lowAngularXLimit.limit, MlpAlpha(lowAngularXColor, alphaMlp), -25, modifiable);
|
||||
joint.lowAngularXLimit = NewJointLimit(newLowAngularXLimit, joint.lowAngularXLimit, -180, 180);
|
||||
|
||||
float newHighAngularXLimit = joint.highAngularXLimit.limit;
|
||||
if (newLowAngularXLimit != 0 || (newLowAngularXLimit == 0 && newHighAngularXLimit != 0)) newHighAngularXLimit = DrawJointLimit(joint as Joint, highX, axis, joint.highAngularXLimit.limit, MlpAlpha(highAngularXColor, alphaMlp), 25, modifiable);
|
||||
joint.highAngularXLimit = NewJointLimit(newHighAngularXLimit, joint.highAngularXLimit, -180, 180);
|
||||
}
|
||||
|
||||
if (joint.angularYMotion == ConfigurableJointMotion.Limited) {
|
||||
float newAngularYLimit = DrawJointLimit(joint as Joint, y, secondaryAxis, -joint.angularYLimit.limit, MlpAlpha(angularYColor, alphaMlp), 25, false);
|
||||
newAngularYLimit = DrawJointLimit(joint as Joint, y, secondaryAxis, -newAngularYLimit, MlpAlpha(angularYColor, alphaMlp), 25, modifiable);
|
||||
joint.angularYLimit = NewJointLimit(newAngularYLimit, joint.angularYLimit, 0, 180);
|
||||
}
|
||||
|
||||
if (joint.angularZMotion == ConfigurableJointMotion.Limited) {
|
||||
float newAngularZLimit = DrawJointLimit(joint as Joint, z, crossAxis, -joint.angularZLimit.limit, MlpAlpha(angularZColor, alphaMlp), 25, false);
|
||||
newAngularZLimit = DrawJointLimit(joint as Joint, z, crossAxis, -newAngularZLimit, MlpAlpha(angularZColor, alphaMlp), 25, modifiable);
|
||||
joint.angularZLimit = NewJointLimit(newAngularZLimit, joint.angularZLimit, 0, 180);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnSceneGUI() {
|
||||
DrawJoint(target as ConfigurableJoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79e48320bea5b4a49a128eabdb1b39b2
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,27 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace RootMotion.Dynamics {
|
||||
|
||||
/// <summary>
|
||||
/// Converting 3D joints to ConfigurableJoints
|
||||
/// </summary>
|
||||
public class ConvertToConfigurableJoints: Editor {
|
||||
|
||||
/// <summary>
|
||||
/// Converts any 3D joints on the selected Transform and it's children to ConfigurableJoints
|
||||
/// </summary>
|
||||
//[MenuItem ("Component/Physics/Convert to ConfigurableJoints")]
|
||||
[MenuItem ("GameObject/Convert to ConfigurableJoints")]
|
||||
public static void ConvertSelected() {
|
||||
if (Selection.activeGameObject == null) {
|
||||
Debug.Log("Please select the root of the ragdoll that you wish to convert to use ConfigurableJoints.");
|
||||
return;
|
||||
}
|
||||
|
||||
// @todo undo
|
||||
|
||||
JointConverter.ToConfigurable(Selection.activeGameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c49a237763c444bda35d3387fed6c35
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RootMotion.Dynamics
|
||||
{
|
||||
public class DragRig : MonoBehaviour
|
||||
{
|
||||
|
||||
private Rigidbody rig;
|
||||
private void Start()
|
||||
{
|
||||
rig = transform.GetComponent<Rigidbody>();
|
||||
}
|
||||
private void OnMouseDrag()
|
||||
{
|
||||
Vector3 cursorPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
||||
rig.AddForce(0.01f * Time.deltaTime * (cursorPos-transform.position).normalized,ForceMode.Force);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b32271905ed4afe44b53328a916f382a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,139 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace RootMotion.Dynamics {
|
||||
|
||||
public class JointInspector : Editor {
|
||||
|
||||
#region Public methods
|
||||
|
||||
private static bool isDragging;
|
||||
|
||||
public static float DrawJointLimit(Joint joint, string label, Vector3 axis, float limit, Color color, float openValue, bool drawHandles) {
|
||||
if (Event.current.type == EventType.MouseDown) isDragging = false;
|
||||
if (Event.current.type == EventType.MouseUp) isDragging = false;
|
||||
|
||||
float radius = HandleUtility.GetHandleSize(joint.transform.position);
|
||||
Vector3 center = joint.transform.TransformPoint(joint.anchor);
|
||||
|
||||
Quaternion axisOffset = Quaternion.AngleAxis(GetViewOffset(joint, axis), axis);
|
||||
Vector3 limitAxis = joint.transform.TransformDirection(axisOffset * axis).normalized;
|
||||
Vector3 limitAxisOrtho = joint.transform.TransformDirection(axisOffset * new Vector3(axis.z, axis.x, axis.y)).normalized;
|
||||
Vector3 limitAxisCross = Vector3.Cross(limitAxis, limitAxisOrtho);
|
||||
|
||||
Handles.color = color;
|
||||
Handles.DrawSolidArc(center, limitAxis, limitAxisCross, -limit, radius);
|
||||
|
||||
Handles.color = new Color(color.r, color.g, color.b, 1f);
|
||||
GUI.color = new Color(color.r, color.g, color.b, 1f);
|
||||
|
||||
//if (limitAxis != Vector3.zero) Handles.CircleCap(0, center, Quaternion.LookRotation(limitAxis), radius);
|
||||
|
||||
Quaternion angleAxis = Quaternion.AngleAxis(-limit, limitAxis);
|
||||
Vector3 handleVector = angleAxis * limitAxisCross;
|
||||
|
||||
Quaternion handleRotation = Quaternion.AngleAxis(-limit, limitAxis);
|
||||
if (limitAxisCross != Vector3.zero && limitAxisOrtho != Vector3.zero) {
|
||||
handleRotation = Quaternion.AngleAxis(-limit, limitAxis) * Quaternion.LookRotation(limitAxisCross) * Quaternion.LookRotation(limitAxisOrtho);
|
||||
}
|
||||
|
||||
if (!drawHandles) return limit;
|
||||
|
||||
float newLimit = Inspector.ScaleValueHandleSphere(limit, center + handleVector * radius, Quaternion.identity, radius, 1);
|
||||
//float newLimit = Handles.han(handleRotation, center + handleVector * radius, limit);
|
||||
|
||||
string labelInfo = label;
|
||||
|
||||
if (newLimit == 0) {
|
||||
labelInfo = "Open " + label;
|
||||
if (Inspector.SphereButton(center + handleVector * radius, handleRotation, radius * 0.2f, radius * 0.07f)) {
|
||||
newLimit = openValue;
|
||||
}
|
||||
}
|
||||
|
||||
Handles.Label(center + handleVector * radius * 1.2f, labelInfo);
|
||||
|
||||
if (newLimit != limit) {
|
||||
if (!isDragging) {
|
||||
Undo.RecordObject(joint, "Change Joint Limits");
|
||||
isDragging = true;
|
||||
}
|
||||
}
|
||||
|
||||
GUI.color = Color.white;
|
||||
return newLimit;
|
||||
}
|
||||
|
||||
public static SoftJointLimit NewJointLimit(float limit, SoftJointLimit referenceJointLimit, float min, float max) {
|
||||
SoftJointLimit newJointLimit = new SoftJointLimit();
|
||||
newJointLimit.limit = Mathf.Clamp(limit, min, max);
|
||||
newJointLimit.bounciness = referenceJointLimit.bounciness;
|
||||
return newJointLimit;
|
||||
}
|
||||
|
||||
public static Color MlpAlpha(Color color, float alphaMlp) {
|
||||
return new Color(color.r, color.g, color.b, color.a * alphaMlp);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static float GetViewOffset(Joint joint, Vector3 axis) {
|
||||
if (joint.connectedBody == null) return 0;
|
||||
|
||||
Vector3 directionAxis = joint.transform.right;
|
||||
Axis viewAxis = GetViewAxis(joint);
|
||||
|
||||
switch(viewAxis) {
|
||||
case Axis.Y: directionAxis = joint.transform.up; break;
|
||||
case Axis.Z: directionAxis = joint.transform.forward; break;
|
||||
}
|
||||
|
||||
if (FlipAxis(joint, viewAxis)) directionAxis = -directionAxis;
|
||||
|
||||
Vector3 axisWorld = joint.transform.TransformDirection(axis).normalized;
|
||||
Vector3 axisWorldOrtho = joint.transform.TransformDirection(new Vector3(axis.z, axis.x, axis.y)).normalized;
|
||||
|
||||
float dot = Vector3.Dot(axisWorldOrtho, directionAxis);
|
||||
float dot2 = Vector3.Dot(axisWorldOrtho, Vector3.Cross(directionAxis, axisWorld));
|
||||
|
||||
if (dot >= 0.5f) return -90;
|
||||
if (dot2 >= 0.5f) return 0;
|
||||
if (dot < -0.5f) return 90;
|
||||
if (dot2 < -0.5f) return 180;
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static Axis GetViewAxis(Joint joint) {
|
||||
if (joint.connectedBody == null) return Axis.Z;
|
||||
|
||||
CapsuleCollider capsule = joint.GetComponent<CapsuleCollider>();
|
||||
if (capsule != null && capsule.center != Vector3.zero) return AxisTools.ToAxis(capsule.center);
|
||||
else {
|
||||
BoxCollider box = joint.GetComponent<BoxCollider>();
|
||||
if (box != null && box.center != Vector3.zero) return AxisTools.ToAxis(box.center);
|
||||
}
|
||||
|
||||
return AxisTools.GetAxisToPoint(joint.transform, joint.connectedBody.worldCenterOfMass);
|
||||
}
|
||||
|
||||
private static bool FlipAxis(Joint joint, Axis axis) {
|
||||
Vector3 direction = Vector3.Normalize(joint.transform.position - joint.connectedBody.worldCenterOfMass);
|
||||
|
||||
CapsuleCollider capsuleCollider = joint.GetComponent<CapsuleCollider>();
|
||||
if (capsuleCollider != null) {
|
||||
direction = Vector3.Normalize(joint.transform.position - (joint.transform.position - (joint.transform.rotation * capsuleCollider.center)));
|
||||
}
|
||||
|
||||
switch(axis) {
|
||||
case Axis.X:
|
||||
return Vector3.Dot(joint.transform.right, direction) < 0;
|
||||
case Axis.Y:
|
||||
return Vector3.Dot(joint.transform.up, direction) < 0;
|
||||
default: return Vector3.Dot(joint.transform.forward, direction) < 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2c166e20a769d4ec3a0f504c4fa3ffe7
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
@@ -0,0 +1,276 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEditor;
|
||||
|
||||
namespace RootMotion.Dynamics {
|
||||
|
||||
public enum JointAxis {
|
||||
Primary,
|
||||
Secondary,
|
||||
Tertiary
|
||||
}
|
||||
|
||||
// Tools for editing Joints.
|
||||
public class JointTools: Editor {
|
||||
|
||||
public static Quaternion LocalToJointSpace(ConfigurableJoint joint) {
|
||||
Vector3 forward = Vector3.Cross (joint.axis, joint.secondaryAxis).normalized;
|
||||
Vector3 up = Vector3.Cross(forward, joint.axis).normalized;
|
||||
|
||||
if (forward == up) {
|
||||
Debug.LogWarning("Joint " + joint.name + " secondaryAxis is in the exact same direction as it's axis. Please make sure they are not aligned.");
|
||||
}
|
||||
|
||||
return Quaternion.LookRotation(forward, up);
|
||||
}
|
||||
|
||||
public static Quaternion LocalToJointSpace(CharacterJoint joint) {
|
||||
Vector3 forward = Vector3.Cross (joint.axis, joint.swingAxis).normalized;
|
||||
Vector3 up = Vector3.Cross(forward, joint.axis).normalized;
|
||||
|
||||
if (forward == up) {
|
||||
Debug.LogWarning("Joint " + joint.name + " swingAxis is in the exact same direction as it's axis. Please make sure they are not aligned.");
|
||||
}
|
||||
|
||||
return Quaternion.LookRotation(forward, up);
|
||||
}
|
||||
|
||||
public static Quaternion JointToLocalSpace(ConfigurableJoint joint) {
|
||||
return Quaternion.Inverse(LocalToJointSpace(joint));
|
||||
}
|
||||
|
||||
public static Quaternion JointToLocalSpace(CharacterJoint joint) {
|
||||
return Quaternion.Inverse(LocalToJointSpace(joint));
|
||||
}
|
||||
|
||||
public static JointAxis GetJointAxis(ConfigurableJoint joint, Vector3 vLocal) {
|
||||
return GetJointAxis(joint.axis, joint.secondaryAxis, vLocal);
|
||||
}
|
||||
|
||||
public static JointAxis GetJointAxis(CharacterJoint joint, Vector3 vLocal) {
|
||||
return GetJointAxis(joint.axis, joint.swingAxis, vLocal);
|
||||
}
|
||||
|
||||
public static JointAxis GetJointAxis(Vector3 axis, Vector3 secondaryAxis, Vector3 vLocal) {
|
||||
vLocal = vLocal.normalized;
|
||||
|
||||
float axisDot = Mathf.Abs(Vector3.Dot(axis, vLocal));
|
||||
float secondaryAxisDot = Mathf.Abs(Vector3.Dot(secondaryAxis, vLocal));
|
||||
float tertiaryAxisDot = Mathf.Abs(Vector3.Dot(Vector3.Cross(axis, secondaryAxis), vLocal));
|
||||
|
||||
if (axisDot > secondaryAxisDot && axisDot > tertiaryAxisDot) return JointAxis.Primary;
|
||||
if (secondaryAxisDot > axisDot && secondaryAxisDot > tertiaryAxisDot) return JointAxis.Secondary;
|
||||
return JointAxis.Tertiary;
|
||||
}
|
||||
|
||||
public static JointAxis GetSymmetricJointAxis(ConfigurableJoint joint, JointAxis jointAxis, ConfigurableJoint symmetricJoint, Transform root) {
|
||||
return GetSymmetricJointAxis(joint.transform, joint.axis, joint.secondaryAxis, jointAxis, symmetricJoint.transform, symmetricJoint.axis, symmetricJoint.secondaryAxis, root);
|
||||
}
|
||||
|
||||
public static JointAxis GetSymmetricJointAxis(CharacterJoint joint, JointAxis jointAxis, CharacterJoint symmetricJoint, Transform root) {
|
||||
return GetSymmetricJointAxis(joint.transform, joint.axis, joint.swingAxis, jointAxis, symmetricJoint.transform, symmetricJoint.axis, symmetricJoint.swingAxis, root);
|
||||
}
|
||||
|
||||
public static JointAxis GetSymmetricJointAxis(Transform jointTransform, Vector3 axis, Vector3 secondaryAxis, JointAxis jointAxis, Transform symmetricTransform, Vector3 symmetricAxis, Vector3 symmetricSecondaryAxis, Transform root) {
|
||||
Vector3 a = JointAxisToVector3(axis, secondaryAxis, jointAxis);
|
||||
Vector3 aWorld = jointTransform.rotation * a;
|
||||
|
||||
Vector3 aWorldMirror = SymmetryTools.Mirror(aWorld, root);
|
||||
Vector3 aLocalMirror = Quaternion.Inverse(symmetricTransform.rotation) * aWorldMirror;
|
||||
|
||||
Vector3 sAVector = AxisTools.GetAxisVectorToDirection(symmetricTransform, aWorldMirror);
|
||||
Vector3 sA = Vector3.Project(aLocalMirror, sAVector);
|
||||
|
||||
return GetJointAxis(symmetricAxis, symmetricSecondaryAxis, sA);
|
||||
}
|
||||
|
||||
public static Vector3 JointAxisToVector3(ConfigurableJoint joint, JointAxis jointAxis) {
|
||||
return JointAxisToVector3(joint.axis, joint.secondaryAxis, jointAxis);
|
||||
}
|
||||
|
||||
public static Vector3 JointAxisToVector3(CharacterJoint joint, JointAxis jointAxis) {
|
||||
return JointAxisToVector3(joint.axis, joint.swingAxis, jointAxis);
|
||||
}
|
||||
|
||||
public static Vector3 JointAxisToVector3(Vector3 axis, Vector3 secondaryAxis, JointAxis jointAxis) {
|
||||
switch(jointAxis) {
|
||||
case JointAxis.Primary: return axis;
|
||||
case JointAxis.Secondary: return secondaryAxis;
|
||||
default: return Vector3.Cross(axis, secondaryAxis);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ApplyXDeltaToJointLimit(ref ConfigurableJoint joint, float delta, JointAxis jointAxis, bool low) {
|
||||
switch(jointAxis) {
|
||||
case JointAxis.Primary:
|
||||
if (low) {
|
||||
SoftJointLimit lowX = joint.lowAngularXLimit;
|
||||
lowX.limit += delta;
|
||||
joint.lowAngularXLimit = lowX;
|
||||
} else {
|
||||
SoftJointLimit highX = joint.highAngularXLimit;
|
||||
highX.limit -= delta;
|
||||
joint.highAngularXLimit = highX;
|
||||
}
|
||||
break;
|
||||
case JointAxis.Secondary:
|
||||
SoftJointLimit y = joint.angularYLimit;
|
||||
y.limit += delta * 0.5f;
|
||||
joint.angularYLimit = y;
|
||||
break;
|
||||
case JointAxis.Tertiary:
|
||||
SoftJointLimit z = joint.angularZLimit;
|
||||
z.limit += delta * 0.5f;
|
||||
joint.angularZLimit = z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ApplyXDeltaToJointLimit(ref CharacterJoint joint, float delta, JointAxis jointAxis, bool low) {
|
||||
switch(jointAxis) {
|
||||
case JointAxis.Primary:
|
||||
if (low) {
|
||||
SoftJointLimit lowX = joint.lowTwistLimit;
|
||||
lowX.limit += delta;
|
||||
joint.lowTwistLimit = lowX;
|
||||
} else {
|
||||
SoftJointLimit highX = joint.highTwistLimit;
|
||||
highX.limit -= delta;
|
||||
joint.highTwistLimit = highX;
|
||||
}
|
||||
break;
|
||||
case JointAxis.Secondary:
|
||||
SoftJointLimit y = joint.swing1Limit;
|
||||
y.limit += delta * 0.5f;
|
||||
joint.swing1Limit = y;
|
||||
break;
|
||||
case JointAxis.Tertiary:
|
||||
SoftJointLimit z = joint.swing2Limit;
|
||||
z.limit += delta * 0.5f;
|
||||
joint.swing2Limit = z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ApplyDeltaToJointLimit(ref ConfigurableJoint joint, float delta, JointAxis jointAxis) {
|
||||
switch(jointAxis) {
|
||||
case JointAxis.Primary:
|
||||
SoftJointLimit lowX = joint.lowAngularXLimit;
|
||||
lowX.limit += delta;
|
||||
joint.lowAngularXLimit = lowX;
|
||||
|
||||
SoftJointLimit highX = joint.highAngularXLimit;
|
||||
highX.limit += delta;
|
||||
joint.highAngularXLimit = highX;
|
||||
break;
|
||||
case JointAxis.Secondary:
|
||||
SoftJointLimit y = joint.angularYLimit;
|
||||
y.limit += delta;
|
||||
joint.angularYLimit = y;
|
||||
break;
|
||||
case JointAxis.Tertiary:
|
||||
SoftJointLimit z = joint.angularZLimit;
|
||||
z.limit += delta;
|
||||
joint.angularZLimit = z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void ApplyDeltaToJointLimit(ref CharacterJoint joint, float delta, JointAxis jointAxis) {
|
||||
switch(jointAxis) {
|
||||
case JointAxis.Primary:
|
||||
SoftJointLimit lowX = joint.lowTwistLimit;
|
||||
lowX.limit += delta;
|
||||
joint.lowTwistLimit = lowX;
|
||||
|
||||
SoftJointLimit highX = joint.highTwistLimit;
|
||||
highX.limit += delta;
|
||||
joint.highTwistLimit = highX;
|
||||
break;
|
||||
case JointAxis.Secondary:
|
||||
SoftJointLimit y = joint.swing1Limit;
|
||||
y.limit += delta;
|
||||
joint.swing1Limit = y;
|
||||
break;
|
||||
case JointAxis.Tertiary:
|
||||
SoftJointLimit z = joint.swing2Limit;
|
||||
z.limit += delta;
|
||||
joint.swing2Limit = z;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector3 GetLowXAxisWorld(Joint joint) {
|
||||
return joint.transform.rotation * joint.axis;
|
||||
}
|
||||
|
||||
public static Vector3 GetHighXAxisWorld(Joint joint) {
|
||||
return joint.transform.rotation * -joint.axis;
|
||||
}
|
||||
|
||||
public static void SwitchXY(ref ConfigurableJoint joint) {
|
||||
Undo.RecordObject(joint, "Switch Yellow/Green Joint Axis");
|
||||
|
||||
Vector3 axis = joint.axis;
|
||||
joint.axis = joint.secondaryAxis;
|
||||
joint.secondaryAxis = axis;
|
||||
}
|
||||
|
||||
public static void SwitchXZ(ref ConfigurableJoint joint) {
|
||||
Undo.RecordObject(joint, "Switch Yellow/Blue Joint Axis");
|
||||
|
||||
Vector3 cross = Vector3.Cross(joint.axis, joint.secondaryAxis).normalized;
|
||||
joint.axis = cross;
|
||||
}
|
||||
|
||||
public static void SwitchYZ(ref ConfigurableJoint joint) {
|
||||
Undo.RecordObject(joint, "Switch Green/Blue Joint Axis");
|
||||
|
||||
Vector3 cross = Vector3.Cross(joint.axis, joint.secondaryAxis).normalized;
|
||||
joint.secondaryAxis = cross;
|
||||
}
|
||||
|
||||
public static void SwitchXY(ref CharacterJoint joint) {
|
||||
Undo.RecordObject(joint, "Switch Yellow/Green Joint Axis");
|
||||
|
||||
Vector3 axis = joint.axis;
|
||||
joint.axis = joint.swingAxis;
|
||||
joint.swingAxis = axis;
|
||||
}
|
||||
|
||||
public static void SwitchXZ(ref CharacterJoint joint) {
|
||||
Undo.RecordObject(joint, "Switch Yellow/Blue Joint Axis");
|
||||
|
||||
Vector3 cross = Vector3.Cross(joint.axis, joint.swingAxis).normalized;
|
||||
joint.axis = cross;
|
||||
}
|
||||
|
||||
public static void SwitchYZ(ref CharacterJoint joint) {
|
||||
Undo.RecordObject(joint, "Switch Green/Blue Joint Axis");
|
||||
|
||||
Vector3 cross = Vector3.Cross(joint.axis, joint.swingAxis).normalized;
|
||||
joint.swingAxis = cross;
|
||||
}
|
||||
|
||||
public static void InvertAxis(ref Joint joint) {
|
||||
Undo.RecordObject(joint, "Invert Axis");
|
||||
|
||||
joint.axis = -joint.axis;
|
||||
}
|
||||
|
||||
public static void InvertSecondaryAxis(ref Joint joint) {
|
||||
if (joint is ConfigurableJoint) {
|
||||
var j = joint as ConfigurableJoint;
|
||||
Undo.RecordObject(j, "Invert Secondary Axis");
|
||||
j.secondaryAxis = -j.secondaryAxis;
|
||||
}
|
||||
|
||||
if (joint is CharacterJoint) {
|
||||
var j = joint as CharacterJoint;
|
||||
Undo.RecordObject(j, "Invert Swing Axis");
|
||||
j.swingAxis = -j.swingAxis;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d88fb763cb86436197038e855690500
|
||||
timeCreated: 1444653109
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user