mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-03-22 02:16:16 -05:00
476 lines
15 KiB
C#
476 lines
15 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using UniGLTF;
|
|
using UniHumanoid;
|
|
using Unity.Collections;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
using UnityEngine.UI;
|
|
|
|
namespace VRM.SimpleViewer
|
|
{
|
|
/// <summary>
|
|
/// UI event handling
|
|
/// </summary>
|
|
public class ViewerUI : MonoBehaviour
|
|
{
|
|
[SerializeField]
|
|
TextFields m_texts = default;
|
|
|
|
#region UI
|
|
[SerializeField]
|
|
Text m_version = default;
|
|
|
|
[SerializeField]
|
|
Button m_open = default;
|
|
|
|
[SerializeField]
|
|
Toggle m_enableLipSync = default;
|
|
|
|
[SerializeField]
|
|
Toggle m_enableAutoBlink = default;
|
|
|
|
[SerializeField]
|
|
Toggle m_useUrpMaterial = default;
|
|
|
|
[SerializeField]
|
|
Toggle m_useAsync = default;
|
|
|
|
[SerializeField]
|
|
Toggle m_loadAnimation = default;
|
|
|
|
// SpringBone
|
|
[SerializeField]
|
|
Toggle m_useFastSpringBone = default;
|
|
|
|
[SerializeField]
|
|
Toggle m_springBonePause = default;
|
|
|
|
[SerializeField]
|
|
Toggle m_springBoneScaling = default;
|
|
|
|
[SerializeField]
|
|
Slider m_springExternalX = default;
|
|
[SerializeField]
|
|
Slider m_springExternalY = default;
|
|
[SerializeField]
|
|
Slider m_springExternalZ = default;
|
|
|
|
[SerializeField]
|
|
Button m_reset = default;
|
|
|
|
[SerializeField]
|
|
Button m_reconstruct = default;
|
|
#endregion
|
|
|
|
[SerializeField]
|
|
HumanPoseTransfer m_src = default;
|
|
|
|
[SerializeField]
|
|
GameObject m_target = default;
|
|
|
|
[SerializeField]
|
|
GameObject Root = default;
|
|
|
|
[SerializeField]
|
|
TextAsset m_motion;
|
|
|
|
[Serializable]
|
|
class TextFields
|
|
{
|
|
[SerializeField, Header("Info")]
|
|
Text m_textModelTitle = default;
|
|
[SerializeField]
|
|
Text m_textModelVersion = default;
|
|
[SerializeField]
|
|
Text m_textModelAuthor = default;
|
|
[SerializeField]
|
|
Text m_textModelContact = default;
|
|
[SerializeField]
|
|
Text m_textModelReference = default;
|
|
[SerializeField]
|
|
RawImage m_thumbnail = default;
|
|
|
|
[SerializeField, Header("CharacterPermission")]
|
|
Text m_textPermissionAllowed = default;
|
|
[SerializeField]
|
|
Text m_textPermissionViolent = default;
|
|
[SerializeField]
|
|
Text m_textPermissionSexual = default;
|
|
[SerializeField]
|
|
Text m_textPermissionCommercial = default;
|
|
[SerializeField]
|
|
Text m_textPermissionOther = default;
|
|
|
|
[SerializeField, Header("DistributionLicense")]
|
|
Text m_textDistributionLicense = default;
|
|
[SerializeField]
|
|
Text m_textDistributionOther = default;
|
|
|
|
public void Start()
|
|
{
|
|
m_textModelTitle.text = "";
|
|
m_textModelVersion.text = "";
|
|
m_textModelAuthor.text = "";
|
|
m_textModelContact.text = "";
|
|
m_textModelReference.text = "";
|
|
|
|
m_textPermissionAllowed.text = "";
|
|
m_textPermissionViolent.text = "";
|
|
m_textPermissionSexual.text = "";
|
|
m_textPermissionCommercial.text = "";
|
|
m_textPermissionOther.text = "";
|
|
|
|
m_textDistributionLicense.text = "";
|
|
m_textDistributionOther.text = "";
|
|
}
|
|
|
|
public void UpdateMeta(VRMMetaObject meta)
|
|
{
|
|
m_textModelTitle.text = meta.Title;
|
|
m_textModelVersion.text = meta.Version;
|
|
m_textModelAuthor.text = meta.Author;
|
|
m_textModelContact.text = meta.ContactInformation;
|
|
m_textModelReference.text = meta.Reference;
|
|
|
|
m_textPermissionAllowed.text = meta.AllowedUser.ToString();
|
|
m_textPermissionViolent.text = meta.ViolentUssage.ToString();
|
|
m_textPermissionSexual.text = meta.SexualUssage.ToString();
|
|
m_textPermissionCommercial.text = meta.CommercialUssage.ToString();
|
|
m_textPermissionOther.text = meta.OtherPermissionUrl;
|
|
|
|
m_textDistributionLicense.text = meta.LicenseType.ToString();
|
|
m_textDistributionOther.text = meta.OtherLicenseUrl;
|
|
|
|
m_thumbnail.texture = meta.Thumbnail;
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
class UIFields
|
|
{
|
|
[SerializeField]
|
|
Toggle ToggleMotionTPose = default;
|
|
|
|
[SerializeField]
|
|
Toggle ToggleMotionBVH = default;
|
|
|
|
[SerializeField]
|
|
ToggleGroup ToggleMotion = default;
|
|
|
|
Toggle m_activeToggleMotion = default;
|
|
|
|
public void UpdateToggle(Action onBvh, Action onTPose)
|
|
{
|
|
var value = ToggleMotion.ActiveToggles().FirstOrDefault();
|
|
if (value == m_activeToggleMotion)
|
|
return;
|
|
|
|
m_activeToggleMotion = value;
|
|
if (value == ToggleMotionTPose)
|
|
{
|
|
onTPose();
|
|
}
|
|
else if (value == ToggleMotionBVH)
|
|
{
|
|
onBvh();
|
|
}
|
|
else
|
|
{
|
|
UniGLTFLogger.Log("motion: no toggle");
|
|
}
|
|
}
|
|
}
|
|
[SerializeField]
|
|
UIFields m_ui = default;
|
|
|
|
[SerializeField]
|
|
HumanPoseClip m_pose = default;
|
|
|
|
private void Reset()
|
|
{
|
|
#if UNITY_2022_3_OR_NEWER
|
|
var buttons = GameObject.FindObjectsByType<Button>(FindObjectsSortMode.InstanceID);
|
|
#else
|
|
var buttons = GameObject.FindObjectsOfType<Button>();
|
|
#endif
|
|
m_open = buttons.First(x => x.name == "Open");
|
|
|
|
m_reset = buttons.First(x => x.name == "ResetSpringBone");
|
|
|
|
#if UNITY_2022_3_OR_NEWER
|
|
var toggles = GameObject.FindObjectsByType<Toggle>(FindObjectsSortMode.InstanceID);
|
|
#else
|
|
var toggles = GameObject.FindObjectsOfType<Toggle>();
|
|
#endif
|
|
m_useFastSpringBone = toggles.First(x => x.name == "UseFastSpringBone");
|
|
m_enableLipSync = toggles.First(x => x.name == "EnableLipSync");
|
|
m_enableAutoBlink = toggles.First(x => x.name == "EnableAutoBlink");
|
|
|
|
#if UNITY_2022_3_OR_NEWER
|
|
var texts = GameObject.FindObjectsByType<Text>(FindObjectsSortMode.InstanceID);
|
|
#else
|
|
var texts = GameObject.FindObjectsOfType<Text>();
|
|
#endif
|
|
m_version = texts.First(x => x.name == "Version");
|
|
|
|
#if UNITY_2022_3_OR_NEWER
|
|
m_src = GameObject.FindFirstObjectByType<HumanPoseTransfer>();
|
|
|
|
m_target = GameObject.FindFirstObjectByType<TargetMover>().gameObject;
|
|
#else
|
|
m_src = GameObject.FindObjectOfType<HumanPoseTransfer>();
|
|
|
|
m_target = GameObject.FindObjectOfType<TargetMover>().gameObject;
|
|
#endif
|
|
}
|
|
|
|
Loaded m_loaded;
|
|
|
|
static class ArgumentChecker
|
|
{
|
|
static string[] Supported = {
|
|
".gltf",
|
|
".glb",
|
|
".vrm",
|
|
".zip",
|
|
};
|
|
|
|
static string UnityHubPath => System.Environment.GetEnvironmentVariable("ProgramFiles") + "\\Unity\\Hub";
|
|
|
|
public static bool IsLoadable(string path)
|
|
{
|
|
if (!File.Exists(path))
|
|
{
|
|
// not exists
|
|
return false;
|
|
}
|
|
|
|
if (Application.isEditor)
|
|
{
|
|
// skip editor argument
|
|
// {UnityHub_Resources}\PackageManager\ProjectTemplates\com.unity.template.3d-5.0.4.tgz
|
|
if (path.StartsWith(UnityHubPath))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
var ext = Path.GetExtension(path).ToLower();
|
|
if (!Supported.Contains(ext))
|
|
{
|
|
// unknown extension
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static bool TryGetFirstLoadable(out string cmd)
|
|
{
|
|
foreach (var arg in System.Environment.GetCommandLineArgs())
|
|
{
|
|
if (ArgumentChecker.IsLoadable(arg))
|
|
{
|
|
cmd = arg;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
cmd = default;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private void Awake()
|
|
{
|
|
#if DEBUG
|
|
NativeLeakDetection.Mode = NativeLeakDetectionMode.EnabledWithStackTrace;
|
|
#endif
|
|
}
|
|
|
|
private void Start()
|
|
{
|
|
m_version.text = string.Format("SimpleViewer {0}", PackageVersion.VERSION);
|
|
m_open.onClick.AddListener(OnOpenClicked);
|
|
|
|
m_reset.onClick.AddListener(() => m_loaded?.ResetSpringbone());
|
|
m_reconstruct.onClick.AddListener(() => m_loaded?.ReconstructSpringbone());
|
|
|
|
// load initial bvh
|
|
if (m_motion != null)
|
|
{
|
|
LoadMotion("tmp.bvh", m_motion.text);
|
|
}
|
|
|
|
if (ArgumentChecker.TryGetFirstLoadable(out var cmd))
|
|
{
|
|
LoadPathAsync(cmd);
|
|
}
|
|
|
|
m_texts.Start();
|
|
}
|
|
|
|
private void LoadMotion(string path, string source)
|
|
{
|
|
var context = new UniHumanoid.BvhImporterContext();
|
|
context.Parse(path, source);
|
|
context.Load();
|
|
m_src = context.Root.GetComponent<HumanPoseTransfer>();
|
|
if (m_src == null)
|
|
{
|
|
throw new ArgumentNullException();
|
|
}
|
|
m_loaded?.EnableBvh(m_src);
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (Input.GetKeyDown(KeyCode.Tab))
|
|
{
|
|
if (Root != null) Root.SetActive(!Root.activeSelf);
|
|
}
|
|
|
|
m_ui.UpdateToggle(
|
|
() => m_loaded?.EnableBvh(m_src),
|
|
() => m_loaded?.EnableTPose(m_pose));
|
|
|
|
if (m_loaded != null)
|
|
{
|
|
m_loaded.EnableLipSyncValue = m_enableLipSync.isOn;
|
|
m_loaded.EnableBlinkValue = m_enableAutoBlink.isOn;
|
|
m_loaded.SetSpringboneModelLevel(new UniGLTF.SpringBoneJobs.Blittables.BlittableModelLevel
|
|
(
|
|
externalForce: new Vector3(
|
|
m_springExternalX.value,
|
|
m_springExternalY.value,
|
|
m_springExternalZ.value
|
|
),
|
|
stopSpringBoneWriteback: m_springBonePause.isOn,
|
|
supportsScalingAtRuntime: m_springBoneScaling.isOn
|
|
));
|
|
m_loaded.Update();
|
|
}
|
|
}
|
|
|
|
IEnumerator LoadCoroutine(string url)
|
|
{
|
|
var www = UnityWebRequest.Get(url);
|
|
yield return www.SendWebRequest();
|
|
var task = LoadBytesAsync("WebGL.vrm", www.downloadHandler.data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// for WebGL
|
|
/// call from OpenFile.jslib
|
|
/// </summary>
|
|
public void FileSelected(string url)
|
|
{
|
|
UniGLTFLogger.Log($"FileSelected: {url}");
|
|
StartCoroutine(LoadCoroutine(url));
|
|
}
|
|
|
|
void OnOpenClicked()
|
|
{
|
|
var path = FileUtil.OpenFileDialog("Open VRM", "vrm", "bvh");
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
return;
|
|
}
|
|
LoadPathAsync(path);
|
|
}
|
|
|
|
async void LoadPathAsync(string path)
|
|
{
|
|
if (!File.Exists(path))
|
|
{
|
|
UniGLTFLogger.Warning($"{path} not exists");
|
|
return;
|
|
}
|
|
var bytes = File.ReadAllBytes(path);
|
|
await LoadBytesAsync(path, bytes);
|
|
}
|
|
|
|
public async Task LoadBytesAsync(string path, byte[] bytes)
|
|
{
|
|
var size = bytes != null ? bytes.Length : 0;
|
|
UniGLTFLogger.Log($"LoadModelAsync: {path}: {size}bytes");
|
|
|
|
var ext = Path.GetExtension(path).ToLower();
|
|
if (ext == ".bvh")
|
|
{
|
|
// bvh motion
|
|
LoadMotion(path, File.ReadAllText(path));
|
|
return;
|
|
}
|
|
|
|
// cleanup
|
|
if (m_loaded != null)
|
|
{
|
|
m_loaded.Dispose();
|
|
m_loaded = null;
|
|
}
|
|
|
|
// vrm
|
|
VrmUtility.MaterialGeneratorCallback materialCallback = (glTF_VRM_extensions vrm) => GetVrmMaterialGenerator(m_useUrpMaterial.isOn, vrm);
|
|
VrmUtility.MetaCallback metaCallback = m_texts.UpdateMeta;
|
|
IVrm0XSpringBoneRuntime springboneRuntime = m_useFastSpringBone.isOn ? new Vrm0XFastSpringboneRuntime() : new Vrm0XSpringBoneDefaultRuntime();
|
|
var instance = await VrmUtility.LoadBytesAsync(path, bytes, GetIAwaitCaller(m_useAsync.isOn),
|
|
materialCallback, metaCallback,
|
|
loadAnimation: m_loadAnimation.isOn,
|
|
springboneRuntime: springboneRuntime
|
|
);
|
|
|
|
instance.EnableUpdateWhenOffscreen();
|
|
instance.ShowMeshes();
|
|
|
|
m_loaded = new Loaded(instance, m_src, m_target.transform, springboneRuntime);
|
|
}
|
|
|
|
static IMaterialDescriptorGenerator GetGltfMaterialGenerator(bool useUrp)
|
|
{
|
|
//Could be refactored to no longer need this check using RenderPipelineMaterialDescriptorGeneratorUtility
|
|
if (useUrp)
|
|
{
|
|
return new UrpGltfMaterialDescriptorGenerator();
|
|
}
|
|
else
|
|
{
|
|
return new BuiltInGltfMaterialDescriptorGenerator();
|
|
}
|
|
}
|
|
|
|
static IMaterialDescriptorGenerator GetVrmMaterialGenerator(bool useUrp, VRM.glTF_VRM_extensions vrm)
|
|
{
|
|
//Could be refactored to no longer need this check using VrmRenderPipelineMaterialDescriptorGeneratorDescriptorUtility
|
|
if (useUrp)
|
|
{
|
|
return new VRM.UrpVrmMaterialDescriptorGenerator(vrm);
|
|
}
|
|
else
|
|
{
|
|
return new VRM.BuiltInVrmMaterialDescriptorGenerator(vrm);
|
|
}
|
|
}
|
|
|
|
static IAwaitCaller GetIAwaitCaller(bool useAsync)
|
|
{
|
|
if (useAsync)
|
|
{
|
|
#if UNITY_WEBGL
|
|
return new RuntimeOnlyNoThreadAwaitCaller();
|
|
#else
|
|
return new RuntimeOnlyAwaitCaller();
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
return new ImmediateCaller();
|
|
}
|
|
}
|
|
}
|
|
}
|