mirror of
https://github.com/vrm-c/UniVRM.git
synced 2026-05-14 06:19:47 -05:00
The first person and third person layers used hard coded IDs. If these layers
are already in use in a Unity project, this use can lead to conflicts between
the layers used by UniVRM and those inside the project. This patch allows using
Unity's layer settings to assign layer IDs through names ("VRMFirstPersonOnly"
and "VRMThirdPersonOnly").
If no named layers are found, the predefined layer IDs will be used. Since the
layer IDs are accessed only during model setup, there should be no performance
impact from this change. The names of the fields storing the layer IDs have not
been changed to keep API compatibility.
This issue was originally noticed by @Deatrathias (Virtual_Deat on Twitter).
266 lines
8.5 KiB
C#
266 lines
8.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UniGLTF;
|
|
using UnityEngine;
|
|
|
|
|
|
namespace VRM
|
|
{
|
|
public class VRMFirstPerson : MonoBehaviour
|
|
{
|
|
// If no layer names are set, use the default layer IDs.
|
|
// Otherwise use the two Unity layers called "VRMFirstPersonOnly" and "VRMThirdPersonOnly".
|
|
public static bool TriedSetupLayer = false;
|
|
public static int FIRSTPERSON_ONLY_LAYER = 9;
|
|
public static int THIRDPERSON_ONLY_LAYER = 10;
|
|
|
|
[SerializeField]
|
|
public Transform FirstPersonBone;
|
|
|
|
[SerializeField]
|
|
public Vector3 FirstPersonOffset;
|
|
|
|
[Serializable]
|
|
public struct RendererFirstPersonFlags
|
|
{
|
|
public Renderer Renderer;
|
|
public FirstPersonFlag FirstPersonFlag;
|
|
public Mesh SharedMesh
|
|
{
|
|
get
|
|
{
|
|
var renderer = Renderer as SkinnedMeshRenderer;
|
|
if (renderer != null)
|
|
{
|
|
return renderer.sharedMesh;
|
|
}
|
|
|
|
var filter = Renderer.GetComponent<MeshFilter>();
|
|
if (filter != null)
|
|
{
|
|
return filter.sharedMesh;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
[SerializeField]
|
|
public List<RendererFirstPersonFlags> Renderers = new List<RendererFirstPersonFlags>();
|
|
|
|
static IEnumerable<Transform> Traverse(Transform parent)
|
|
{
|
|
yield return parent;
|
|
|
|
foreach (Transform child in parent)
|
|
{
|
|
foreach (var x in Traverse(child))
|
|
{
|
|
yield return x;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void CopyTo(GameObject _dst, Dictionary<Transform, Transform> map)
|
|
{
|
|
var dst = _dst.AddComponent<VRMFirstPerson>();
|
|
dst.FirstPersonBone = FirstPersonBone;
|
|
dst.FirstPersonOffset = FirstPersonOffset;
|
|
dst.Renderers = Renderers.Select(x =>
|
|
{
|
|
var renderer = map[x.Renderer.transform].GetComponent<Renderer>();
|
|
return new VRMFirstPerson.RendererFirstPersonFlags
|
|
{
|
|
Renderer = renderer,
|
|
FirstPersonFlag = x.FirstPersonFlag,
|
|
};
|
|
}).ToList();
|
|
}
|
|
|
|
public void SetDefault()
|
|
{
|
|
FirstPersonOffset = new Vector3(0, 0.06f, 0);
|
|
var animator = GetComponent<Animator>();
|
|
if (animator != null)
|
|
{
|
|
FirstPersonBone = animator.GetBoneTransform(HumanBodyBones.Head);
|
|
}
|
|
}
|
|
|
|
private void Reset()
|
|
{
|
|
TraverseRenderers();
|
|
}
|
|
|
|
public void TraverseRenderers(VRMImporterContext context = null)
|
|
{
|
|
Renderers = Traverse(transform)
|
|
.Select(x => x.GetComponent<Renderer>())
|
|
.Where(x => x != null)
|
|
.Select(x => new RendererFirstPersonFlags
|
|
{
|
|
Renderer = x,
|
|
FirstPersonFlag = context == null
|
|
? FirstPersonFlag.Auto
|
|
: GetFirstPersonFlag(context, x)
|
|
})
|
|
.ToList()
|
|
;
|
|
}
|
|
|
|
static FirstPersonFlag GetFirstPersonFlag(VRMImporterContext context, Renderer r)
|
|
{
|
|
var mesh = r.transform.GetSharedMesh();
|
|
if (mesh == null)
|
|
{
|
|
return FirstPersonFlag.Auto;
|
|
}
|
|
|
|
var index = context.Meshes.FindIndex(x => x.Mesh == mesh);
|
|
if (index == -1)
|
|
{
|
|
return FirstPersonFlag.Auto;
|
|
}
|
|
|
|
foreach(var x in context.GLTF.extensions.VRM.firstPerson.meshAnnotations)
|
|
{
|
|
if (x.mesh == index)
|
|
{
|
|
return EnumUtil.TryParseOrDefault<FirstPersonFlag>(x.firstPersonFlag);
|
|
}
|
|
}
|
|
|
|
return FirstPersonFlag.Auto;
|
|
}
|
|
|
|
void CreateHeadlessModel(Renderer _renderer, Transform EraseRoot)
|
|
{
|
|
{
|
|
var renderer = _renderer as SkinnedMeshRenderer;
|
|
if (renderer != null)
|
|
{
|
|
CreateHeadlessModelForSkinnedMeshRenderer(renderer, EraseRoot);
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
{
|
|
var renderer = _renderer as MeshRenderer;
|
|
if (renderer != null)
|
|
{
|
|
CreateHeadlessModelForMeshRenderer(renderer, EraseRoot);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ここには来ない
|
|
}
|
|
|
|
public static void SetupLayers()
|
|
{
|
|
if (!TriedSetupLayer) {
|
|
TriedSetupLayer = true;
|
|
int layer = LayerMask.NameToLayer("VRMFirstPersonOnly");
|
|
FIRSTPERSON_ONLY_LAYER = (layer == -1) ? FIRSTPERSON_ONLY_LAYER : layer;
|
|
layer = LayerMask.NameToLayer("VRMThirdPersonOnly");
|
|
THIRDPERSON_ONLY_LAYER = (layer == -1) ? THIRDPERSON_ONLY_LAYER : layer;
|
|
}
|
|
}
|
|
|
|
private static void CreateHeadlessModelForMeshRenderer(MeshRenderer renderer, Transform eraseRoot)
|
|
{
|
|
if (renderer.transform.Ancestors().Any(x => x == eraseRoot))
|
|
{
|
|
// 祖先に削除ボーンが居る
|
|
SetupLayers();
|
|
renderer.gameObject.layer = THIRDPERSON_ONLY_LAYER;
|
|
}
|
|
else
|
|
{
|
|
// 特に変更しない => 両方表示
|
|
}
|
|
}
|
|
|
|
private static void CreateHeadlessModelForSkinnedMeshRenderer(SkinnedMeshRenderer renderer, Transform eraseRoot)
|
|
{
|
|
SetupLayers();
|
|
renderer.gameObject.layer = THIRDPERSON_ONLY_LAYER;
|
|
|
|
var go = new GameObject("_headless_" + renderer.name);
|
|
go.layer = FIRSTPERSON_ONLY_LAYER;
|
|
go.transform.SetParent(renderer.transform, false);
|
|
|
|
var m_eraseBones = renderer.bones.Select(x =>
|
|
{
|
|
var eb = new BoneMeshEraser.EraseBone
|
|
{
|
|
Bone = x,
|
|
};
|
|
|
|
if (eraseRoot != null)
|
|
{
|
|
// 首の子孫を消去
|
|
if (eb.Bone.Ancestor().Any(y => y == eraseRoot))
|
|
{
|
|
//Debug.LogFormat("erase {0}", x);
|
|
eb.Erase = true;
|
|
}
|
|
}
|
|
|
|
return eb;
|
|
})
|
|
.ToArray();
|
|
|
|
var bones = renderer.bones;
|
|
var eraseBones = m_eraseBones
|
|
.Where(x => x.Erase)
|
|
.Select(x => bones.IndexOf(x.Bone))
|
|
.ToArray();
|
|
|
|
var mesh = BoneMeshEraser.CreateErasedMesh(renderer.sharedMesh, eraseBones);
|
|
|
|
var erased = go.AddComponent<SkinnedMeshRenderer>();
|
|
erased.sharedMesh = mesh;
|
|
erased.sharedMaterials = renderer.sharedMaterials;
|
|
erased.bones = renderer.bones;
|
|
erased.rootBone = renderer.rootBone;
|
|
}
|
|
|
|
bool m_done;
|
|
|
|
/// <summary>
|
|
/// 配下のモデルのレイヤー設定など
|
|
/// </summary>
|
|
public void Setup()
|
|
{
|
|
SetupLayers();
|
|
if (m_done) return;
|
|
m_done = true;
|
|
foreach (var x in Renderers)
|
|
{
|
|
switch (x.FirstPersonFlag)
|
|
{
|
|
case FirstPersonFlag.Auto:
|
|
CreateHeadlessModel(x.Renderer, FirstPersonBone);
|
|
break;
|
|
|
|
case FirstPersonFlag.FirstPersonOnly:
|
|
x.Renderer.gameObject.layer = FIRSTPERSON_ONLY_LAYER;
|
|
break;
|
|
|
|
case FirstPersonFlag.ThirdPersonOnly:
|
|
x.Renderer.gameObject.layer = THIRDPERSON_ONLY_LAYER;
|
|
break;
|
|
|
|
case FirstPersonFlag.Both:
|
|
//x.Renderer.gameObject.layer = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|