/**
* Copyright(c) Live2D Inc. All rights reserved.
*
* Use of this source code is governed by the Live2D Open Software license
* that can be found at https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html.
*/
using Live2D.Cubism.Core;
using System;
using System.IO;
using Live2D.Cubism.Framework.MouthMovement;
using Live2D.Cubism.Framework.Physics;
using Live2D.Cubism.Framework.UserData;
using Live2D.Cubism.Framework.Pose;
using Live2D.Cubism.Framework.Expression;
using Live2D.Cubism.Framework.MotionFade;
using Live2D.Cubism.Framework.Raycasting;
using Live2D.Cubism.Rendering;
using Live2D.Cubism.Rendering.Masking;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
namespace Live2D.Cubism.Framework.Json
{
///
/// Exposes moc3.json asset data.
///
[Serializable]
// ReSharper disable once ClassCannotBeInstantiated
public sealed class CubismModel3Json
{
#region Delegates
///
/// Handles the loading of assets.
///
/// The asset type to load.
/// The path to the asset.
///
public delegate object LoadAssetAtPathHandler(Type assetType, string assetPath);
///
/// Picks a for a .
///
/// Event source.
/// Drawable to pick for.
/// Picked material.
public delegate Material MaterialPicker(CubismModel3Json sender, CubismDrawable drawable);
///
/// Picks a for a .
///
/// Event source.
/// Drawable to pick for.
/// Picked texture.
public delegate Texture2D TexturePicker(CubismModel3Json sender, CubismDrawable drawable);
#endregion
#region Load Methods
///
/// Loads a model.json asset.
///
/// The path to the asset.
/// The on success; otherwise.
public static CubismModel3Json LoadAtPath(string assetPath)
{
// Use default asset load handler.
return LoadAtPath(assetPath, BuiltinLoadAssetAtPath);
}
///
/// Loads a model.json asset.
///
/// The path to the asset.
/// Handler for loading assets.
/// The on success; otherwise.
public static CubismModel3Json LoadAtPath(string assetPath, LoadAssetAtPathHandler loadAssetAtPath)
{
// Load Json asset.
var modelJsonAsset = loadAssetAtPath(typeof(string), assetPath) as string;
// Return early in case Json asset wasn't loaded.
if (modelJsonAsset == null)
{
return null;
}
// Deserialize Json.
var modelJson = JsonUtility.FromJson(modelJsonAsset);
// Finalize deserialization.
modelJson.AssetPath = assetPath;
modelJson.LoadAssetAtPath = loadAssetAtPath;
// Set motion references.
var value = CubismJsonParser.ParseFromString(modelJsonAsset);
// Return early if there is no references.
if (!value.Get("FileReferences").GetMap(null).ContainsKey("Motions"))
{
return modelJson;
}
var motionGroupNames = value.Get("FileReferences").Get("Motions").KeySet().ToArray();
modelJson.FileReferences.Motions.GroupNames = motionGroupNames;
var motionGroupNamesCount = motionGroupNames.Length;
modelJson.FileReferences.Motions.Motions = new SerializableMotion[motionGroupNamesCount][];
for (var i = 0; i < motionGroupNamesCount; i++)
{
var motionGroup = value.Get("FileReferences").Get("Motions").Get(motionGroupNames[i]);
var motionCount = motionGroup.GetVector(null).ToArray().Length;
modelJson.FileReferences.Motions.Motions[i] = new SerializableMotion[motionCount];
for (var j = 0; j < motionCount; j++)
{
if (motionGroup.Get(j).GetMap(null).ContainsKey("File"))
{
modelJson.FileReferences.Motions.Motions[i][j].File = motionGroup.Get(j).Get("File").toString();
}
if (motionGroup.Get(j).GetMap(null).ContainsKey("Sound"))
{
modelJson.FileReferences.Motions.Motions[i][j].Sound = motionGroup.Get(j).Get("Sound").toString();
}
if (motionGroup.Get(j).GetMap(null).ContainsKey("FadeInTime"))
{
modelJson.FileReferences.Motions.Motions[i][j].FadeInTime = motionGroup.Get(j).Get("FadeInTime").ToFloat();
}
if (motionGroup.Get(j).GetMap(null).ContainsKey("FadeOutTime"))
{
modelJson.FileReferences.Motions.Motions[i][j].FadeOutTime = motionGroup.Get(j).Get("FadeOutTime").ToFloat();
}
}
}
return modelJson;
}
#endregion
///
/// Path to .
///
public string AssetPath { get; private set; }
///
/// Method for loading assets.
///
private LoadAssetAtPathHandler LoadAssetAtPath { get; set; }
#region Json Data
///
/// The motion3.json format version.
///
[SerializeField]
public int Version;
///
/// The file references.
///
[SerializeField]
public SerializableFileReferences FileReferences;
///
/// Groups.
///
[SerializeField]
public SerializableGroup[] Groups;
///
/// Hit areas.
///
[SerializeField]
public SerializableHitArea[] HitAreas;
#endregion
///
/// The contents of the referenced moc3 asset.
///
///
/// The contents isn't cached internally.
///
public byte[] Moc3
{
get
{
return LoadReferencedAsset(FileReferences.Moc);
}
}
///
/// backing field.
///
[NonSerialized]
private CubismPose3Json _pose3Json;
///
/// The contents of pose3.json asset.
///
public CubismPose3Json Pose3Json
{
get
{
if(_pose3Json != null)
{
return _pose3Json;
}
var jsonString = string.IsNullOrEmpty(FileReferences.Pose) ? null : LoadReferencedAsset(FileReferences.Pose);
_pose3Json = CubismPose3Json.LoadFrom(jsonString);
return _pose3Json;
}
}
///
/// backing field.
///
[NonSerialized]
private CubismExp3Json[] _expression3Jsons;
///
/// The referenced expression assets.
///
///
/// The references aren't cached internally.
///
public CubismExp3Json[] Expression3Jsons
{
get
{
// Fail silently...
if(FileReferences.Expressions == null)
{
return null;
}
// Load expression only if necessary.
if (_expression3Jsons == null)
{
_expression3Jsons = new CubismExp3Json[FileReferences.Expressions.Length];
for (var i = 0; i < _expression3Jsons.Length; ++i)
{
var expressionJson = (string.IsNullOrEmpty(FileReferences.Expressions[i].File))
? null
: LoadReferencedAsset(FileReferences.Expressions[i].File);
_expression3Jsons[i] = CubismExp3Json.LoadFrom(expressionJson);
}
}
return _expression3Jsons;
}
}
///
/// The contents of physics3.json asset.
///
public string Physics3Json
{
get
{
return string.IsNullOrEmpty(FileReferences.Physics) ? null : LoadReferencedAsset(FileReferences.Physics);
}
}
public string UserData3Json
{
get
{
return string.IsNullOrEmpty(FileReferences.UserData) ? null : LoadReferencedAsset(FileReferences.UserData);
}
}
///
/// The contents of cdi3.json asset.
///
public string DisplayInfo3Json
{
get
{
return string.IsNullOrEmpty(FileReferences.DisplayInfo) ? null : LoadReferencedAsset(FileReferences.DisplayInfo);
}
}
///
/// backing field.
///
[NonSerialized]
private Texture2D[] _textures;
///
/// The referenced texture assets.
///
///
/// The references aren't cached internally.
///
public Texture2D[] Textures
{
get
{
// Load textures only if necessary.
if (_textures == null)
{
_textures = new Texture2D[FileReferences.Textures.Length];
for (var i = 0; i < _textures.Length; ++i)
{
_textures[i] = LoadReferencedAsset(FileReferences.Textures[i]);
}
}
return _textures;
}
}
#region Constructors
///
/// Makes construction only possible through factories.
///
private CubismModel3Json()
{
}
#endregion
///
/// Instantiates a model source and a model with the default texture set.
///
/// Should import as original workflow.
/// The instantiated model on success; otherwise.
public CubismModel ToModel(bool shouldImportAsOriginalWorkflow = false)
{
return ToModel(CubismBuiltinPickers.MaterialPicker, CubismBuiltinPickers.TexturePicker, shouldImportAsOriginalWorkflow);
}
///
/// Instantiates a model source and a model.
///
/// The material mapper to use.
/// The texture mapper to use.
/// Should import as original workflow.
/// The instantiated model on success; otherwise.
public CubismModel ToModel(MaterialPicker pickMaterial, TexturePicker pickTexture, bool shouldImportAsOriginalWorkflow = false)
{
// Initialize model source and instantiate it.
var mocAsBytes = Moc3;
if (mocAsBytes == null)
{
return null;
}
var moc = CubismMoc.CreateFrom(mocAsBytes);
var model = CubismModel.InstantiateFrom(moc);
model.name = Path.GetFileNameWithoutExtension(FileReferences.Moc);
#if UNITY_EDITOR
// Add parameters and parts inspectors.
model.gameObject.AddComponent();
model.gameObject.AddComponent();
#endif
// Create renderers.
var rendererController = model.gameObject.AddComponent();
var renderers = rendererController.Renderers;
var drawables = model.Drawables;
// Initialize materials.
for (var i = 0; i < renderers.Length; ++i)
{
renderers[i].Material = pickMaterial(this, drawables[i]);
}
// Initialize textures.
for (var i = 0; i < renderers.Length; ++i)
{
renderers[i].MainTexture = pickTexture(this, drawables[i]);
}
// Initialize drawables.
if(HitAreas != null)
{
for (var i = 0; i < HitAreas.Length; i++)
{
for (var j = 0; j < drawables.Length; j++)
{
if (drawables[j].Id == HitAreas[i].Id)
{
// Add components for hit judgement to HitArea target Drawables.
var hitDrawable = drawables[j].gameObject.AddComponent();
hitDrawable.Name = HitAreas[i].Name;
drawables[j].gameObject.AddComponent();
break;
}
}
}
}
//Load from cdi3.json
var DisplayInfo3JsonAsString = DisplayInfo3Json;
var cdi3Json = CubismDisplayInfo3Json.LoadFrom(DisplayInfo3JsonAsString);
// Initialize groups.
var parameters = model.Parameters;
for (var i = 0; i < parameters.Length; ++i)
{
if (IsParameterInGroup(parameters[i], "EyeBlink"))
{
if (model.gameObject.GetComponent() == null)
{
model.gameObject.AddComponent();
}
parameters[i].gameObject.AddComponent();
}
// Set up mouth parameters.
if (IsParameterInGroup(parameters[i], "LipSync"))
{
if (model.gameObject.GetComponent() == null)
{
model.gameObject.AddComponent();
}
parameters[i].gameObject.AddComponent();
}
// Setting up the parameter name for display.
if (cdi3Json != null)
{
var cubismDisplayInfoParameterName = parameters[i].gameObject.AddComponent();
cubismDisplayInfoParameterName.Name = cdi3Json.Parameters[i].Name;
cubismDisplayInfoParameterName.DisplayName = string.Empty;
}
}
// Setting up the part name for display.
if (cdi3Json != null)
{
// Initialize groups.
var parts = model.Parts;
for (var i = 0; i < parts.Length; i++)
{
var cubismDisplayInfoPartNames = parts[i].gameObject.AddComponent();
cubismDisplayInfoPartNames.Name = cdi3Json.Parts[i].Name;
cubismDisplayInfoPartNames.DisplayName = string.Empty;
}
}
// Add mask controller if required.
for (var i = 0; i < drawables.Length; ++i)
{
if (!drawables[i].IsMasked)
{
continue;
}
// Add controller exactly once...
model.gameObject.AddComponent();
break;
}
// Add original workflow component if is original workflow.
if(shouldImportAsOriginalWorkflow)
{
// Add cubism update manager.
var updateManager = model.gameObject.GetComponent();
if(updateManager == null)
{
model.gameObject.AddComponent();
}
// Add parameter store.
var parameterStore = model.gameObject.GetComponent();
if(parameterStore == null)
{
parameterStore = model.gameObject.AddComponent();
}
// Add pose controller.
var poseController = model.gameObject.GetComponent();
if(poseController == null)
{
poseController = model.gameObject.AddComponent();
}
// Add expression controller.
var expressionController = model.gameObject.GetComponent();
if(expressionController == null)
{
expressionController = model.gameObject.AddComponent();
}
// Add fade controller.
var motionFadeController = model.gameObject.GetComponent();
if(motionFadeController == null)
{
motionFadeController = model.gameObject.AddComponent();
}
}
// Initialize physics if JSON exists.
var physics3JsonAsString = Physics3Json;
if (!string.IsNullOrEmpty(physics3JsonAsString))
{
var physics3Json = CubismPhysics3Json.LoadFrom(physics3JsonAsString);
var physicsController = model.gameObject.GetComponent();
if (physicsController == null)
{
physicsController = model.gameObject.AddComponent();
}
physicsController.Initialize(physics3Json.ToRig());
}
var userData3JsonAsString = UserData3Json;
if (!string.IsNullOrEmpty(userData3JsonAsString))
{
var userData3Json = CubismUserData3Json.LoadFrom(userData3JsonAsString);
var drawableBodies = userData3Json.ToBodyArray(CubismUserDataTargetType.ArtMesh);
for (var i = 0; i < drawables.Length; ++i)
{
var index = GetBodyIndexById(drawableBodies, drawables[i].Id);
if (index >= 0)
{
var tag = drawables[i].gameObject.GetComponent();
if (tag == null)
{
tag = drawables[i].gameObject.AddComponent();
}
tag.Initialize(drawableBodies[index]);
}
}
}
if (model.gameObject.GetComponent() == null)
{
model.gameObject.AddComponent();
}
// Make sure model is 'fresh'
model.ForceUpdateNow();
return model;
}
#region Helper Methods
///
/// Type-safely loads an asset.
///
/// Asset type.
/// Path to asset.
/// The asset on success; otherwise.
private T LoadReferencedAsset(string referencedFile) where T : class
{
var assetPath = Path.GetDirectoryName(AssetPath) + "/" + referencedFile;
return LoadAssetAtPath(typeof(T), assetPath) as T;
}
///
/// Builtin method for loading assets.
///
/// Asset type.
/// Path to asset.
/// The asset on success; otherwise.
private static object BuiltinLoadAssetAtPath(Type assetType, string assetPath)
{
// Explicitly deal with byte arrays.
if (assetType == typeof(byte[]))
{
#if UNITY_EDITOR
return File.ReadAllBytes(assetPath);
#else
var textAsset = Resources.Load(assetPath, typeof(TextAsset)) as TextAsset;
return (textAsset != null)
? textAsset.bytes
: null;
#endif
}
else if (assetType == typeof(string))
{
#if UNITY_EDITOR
return File.ReadAllText(assetPath);
#else
var textAsset = Resources.Load(assetPath, typeof(TextAsset)) as TextAsset;
return (textAsset != null)
? textAsset.text
: null;
#endif
}
#if UNITY_EDITOR
return AssetDatabase.LoadAssetAtPath(assetPath, assetType);
#else
return Resources.Load(assetPath, assetType);
#endif
}
///
/// Checks whether the parameter is an eye blink parameter.
///
/// Parameter to check.
/// Name of group to query for.
/// if parameter is an eye blink parameter; otherwise.
private bool IsParameterInGroup(CubismParameter parameter, string groupName)
{
// Return early if groups aren't available...
if (Groups == null || Groups.Length == 0)
{
return false;
}
for (var i = 0; i < Groups.Length; ++i)
{
if (Groups[i].Name != groupName)
{
continue;
}
if(Groups[i].Ids != null)
{
for (var j = 0; j < Groups[i].Ids.Length; ++j)
{
if (Groups[i].Ids[j] == parameter.name)
{
return true;
}
}
}
}
return false;
}
///
/// Get body index from body array by Id.
///
/// Target body array.
/// Id for find.
/// Array index if Id found; -1 otherwise.
private int GetBodyIndexById(CubismUserDataBody[] bodies, string id)
{
for (var i = 0; i < bodies.Length; ++i)
{
if (bodies[i].Id == id)
{
return i;
}
}
return -1;
}
#endregion
#region Json Helpers
///
/// File references data.
///
[Serializable]
public struct SerializableFileReferences
{
///
/// Relative path to the moc3 asset.
///
[SerializeField]
public string Moc;
///
/// Relative paths to texture assets.
///
[SerializeField]
public string[] Textures;
///
/// Relative path to the pose3.json.
///
[SerializeField]
public string Pose;
///
/// Relative path to the expression asset.
///
[SerializeField]
public SerializableExpression[] Expressions;
///
/// Relative path to the pose motion3.json.
///
[SerializeField]
public SerializableMotions Motions;
///
/// Relative path to the physics asset.
///
[SerializeField]
public string Physics;
///
/// Relative path to the user data asset.
///
[SerializeField]
public string UserData;
///
/// Relative path to the cdi3.json.
///
[SerializeField]
public string DisplayInfo;
}
///
/// Group data.
///
[Serializable]
public struct SerializableGroup
{
///
/// Target type.
///
[SerializeField]
public string Target;
///
/// Group name.
///
[SerializeField]
public string Name;
///
/// Referenced IDs.
///
[SerializeField]
public string[] Ids;
}
///
/// Expression data.
///
[Serializable]
public struct SerializableExpression
{
///
/// Expression Name.
///
[SerializeField]
public string Name;
///
/// Expression File.
///
[SerializeField]
public string File;
///
/// Expression FadeInTime.
///
[SerializeField]
public float FadeInTime;
///
/// Expression FadeOutTime.
///
[SerializeField]
public float FadeOutTime;
}
///
/// Motion data.
///
[Serializable]
public struct SerializableMotions
{
///
/// Motion group names.
///
[SerializeField]
public string[] GroupNames;
///
/// Motion groups.
///
[SerializeField]
public SerializableMotion[][] Motions;
}
///
/// Motion data.
///
[Serializable]
public struct SerializableMotion
{
///
/// File path.
///
[SerializeField]
public string File;
///
/// Sound path.
///
[SerializeField]
public string Sound;
///
/// Fade in time.
///
[SerializeField]
public float FadeInTime;
///
/// Fade out time.
///
[SerializeField]
public float FadeOutTime;
}
///
/// Hit Area.
///
[Serializable]
public struct SerializableHitArea
{
///
/// Hit area name.
///
[SerializeField]
public string Name;
///
/// Hit area id.
///
[SerializeField]
public string Id;
}
#endregion
}
}