Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ protected virtual void Awake()
_saveFolderProvider = Singletons.GetSingleton<ISaveFolderProvider>();
_selection = this.GetCharacterCreatorComponent<ICustomizationSelection>();
_dataRepo = this.GetComponentInParent<ICustomizationSelectedDataRepository>();
_tooltip = GetComponent<SettableTooltip>();
// These GameObjects have multiple of the same component on them, so we need this ugly code to find the right ones.
_tooltip = GetComponent<SettableTooltip>();
// These GameObjects have multiple of the same component on them, so we need this ugly code to find the right ones.
ApplySliderAsScaleBase[] applySliders = _headSizeApplyGameObject.GetComponents<ApplySliderAsScaleBase>();
foreach (ApplySliderAsScaleBase slider in applySliders)
{
Expand Down Expand Up @@ -109,13 +109,38 @@ public float GetHeadEarLength()

protected string GetSavePath()
{
var selected = _selection.Selected;
var name = (selected != null) ? selected.CachedData.Name : "UnnamedYinglet";
var sanitizedName = System.Text.RegularExpressions.Regex.Replace(name, "[^a-zA-Z0-9_-]", "");
var folderName = sanitizedName + "-" + System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
CachedYingletReference yingRef = _selection.Selected;
string name = (yingRef != null) ? yingRef.CachedData.Name : "UnnamedYinglet";
string sanitizedName = System.Text.RegularExpressions.Regex.Replace(name, "[^a-zA-Z0-9_-]", "");
string folderName = sanitizedName + "-" + System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss");
return System.IO.Path.Combine(_saveFolderProvider.ExportsFolderPath, folderName);
}

protected Texture2D GetThumbnailTexture()
{
CachedYingletReference yingRef = _selection.Selected;
IYingSnapshotManager snapshotManager = Singletons.GetSingleton<IYingSnapshotManager>();
using (IYingSnapshotRenderTexture snapshot = snapshotManager.GetRenderTexture(yingRef))
{
RenderTexture renderTex = snapshot.RenderTexture;
if (renderTex == null)
{
return null;
}
// Convert RenderTexture to Texture2D
RenderTexture.active = renderTex;
// Hard-code square thumbnail size. The VRM specification says: "The thumbnail image must be square".
int newSize = renderTex.height * 3 / 4;
int offsetX = (renderTex.width - newSize) / 2;
int offsetY = 0; // Align to bottom (Unity's texture coordinate system is bottom-left origin).
Texture2D thumbnail = new Texture2D(newSize, newSize, TextureFormat.RGBA32, false);
thumbnail.ReadPixels(new Rect(offsetX, offsetY, newSize, newSize), 0, 0);
thumbnail.Apply();
RenderTexture.active = null;
return thumbnail;
}
}

protected void EmitExportEvent()
{
OnExport.Invoke();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ public class ExportToModelOnButtonClick : ExportOnButtonClickBase
private enum ExportJSONModelFormat
{
NONE,
G3MF, // .g3b or .g3tf if shift is pressed
GLTF, // .glb or .gltf if shift is pressed
VRM_0_x, // .vrm 0.x or .gltf if shift is pressed
VRM_1_0, // .vrm 1.0 or .gltf if shift is pressed
G3MF, // .g3b or .g3tf if shift is pressed
GLTF, // .glb or .gltf if shift is pressed
VRM_0_x, // .vrm 0.x or .gltf if shift is pressed
VRM_1_0, // .vrm 1.0 or .gltf if shift is pressed
}
[SerializeField] private ExportJSONModelFormat _exportFormat = ExportJSONModelFormat.NONE;
[SerializeField] private JigglePhysics.JiggleRigBuilder _jiggleRigBuilder;
Expand Down Expand Up @@ -61,12 +61,12 @@ protected override void OnExportButtonClicked()
{
Debug.LogError("Yinglet root is not assigned.");
return;
}
// Prepare data.
}
// Prepare data.
ModelMaterial.pupilTexture = _pupilTexture;
MeshObjectOptimization meshObjOpt = (MeshObjectOptimization)_meshOptimizationDropdown.value;
EyeExpression eyeExp = GetEyeExpression();
// Begin export.
EyeExpression eyeExp = GetEyeExpression();
// Begin export.
ModelDocument yinglet = ModelDocument.CreateFromYingletSkeleton(_skeletonHips);
yinglet.SetExplicitBoneLengths(GetHeadAntennaeLength(), GetHeadEarLength());
yinglet.ConvertYingletMeshes(_yingletRoot, meshObjOpt, eyeExp);
Expand All @@ -93,13 +93,14 @@ protected override void OnExportButtonClicked()
SetFloatPrecisionForModelAccessors(baseFormat, _floatPrecisionDropdown.value);
yinglet.PerformOptionalCleanups();
yinglet.EncodeMeshDataIntoAccessors(baseFormat);
yinglet.ExportTextures(_imageFormatDropdown.value);
yinglet.EncodeAnimationAccessors(baseFormat);
string savePath = GetSavePath();
// Note: The non-VRM 0.x formats all include the VRM 1.0 metadata.
// This is because VRM 1.0 is a clean superset of the standard rig, so
// there is no downside to including the data, and it could be used outside
// of VRM applications, like reading the toon materials or spring bone data.
yinglet.EncodeTextures(_imageFormatDropdown.value);
yinglet.EncodeThumbnail(GetThumbnailTexture(), _imageFormatDropdown.value);
string savePath = GetSavePath();
// Note: The non-VRM 0.x formats all include the VRM 1.0 metadata.
// This is because VRM 1.0 is a clean superset of the standard rig, so
// there is no downside to including the data, and it could be used outside
// of VRM applications, like reading the toon materials or spring bone data.
switch (_exportFormat)
{
case ExportJSONModelFormat.NONE:
Expand All @@ -123,16 +124,16 @@ protected override void OnExportButtonClicked()

private static void SetFloatPrecisionForModelAccessors(ModelBaseFormat format, int floatPrecisionDropdownValue)
{
if (floatPrecisionDropdownValue == 0) // Automatic
{
// Automatic based on format: G3MF = float16, glTF = float32.
if (floatPrecisionDropdownValue == 0) // Automatic
{
// Automatic based on format: G3MF = float16, glTF = float32.
floatPrecisionDropdownValue = (format == ModelBaseFormat.G3MF) ? 1 : 2;
}
if (floatPrecisionDropdownValue == 1) // Force 16-bit (half)
if (floatPrecisionDropdownValue == 1) // Force 16-bit (half)
{
ModelAccessor.preferredFloatComponentType = "float16";
}
else if (floatPrecisionDropdownValue == 2) // Force 32-bit (single)
else if (floatPrecisionDropdownValue == 2) // Force 32-bit (single)
{
ModelAccessor.preferredFloatComponentType = "float32";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public class ModelDocument
/// </summary>
private HashSet<string> uniqueNames = new HashSet<string>();
private Dictionary<int, int> nodeIndexToJointMap = null;
private int thumbnailTextureAndImageIndex = -1;

/// <summary>
/// If true, inject extra LowerLeg nodes between UpperLeg and LowerLeg1 to improve compatibility
Expand Down Expand Up @@ -458,6 +459,18 @@ public string ReserveUniqueName(string desiredName)
return uniqueName;
}

public ModelAnimation FindAnimationByName(string name)
{
for (int i = 0; i < animations.Count; i++)
{
if (animations[i].name == name)
{
return animations[i];
}
}
return null;
}

public int FindNodeIndexByName(string name)
{
for (int i = 0; i < nodes.Count; i++)
Expand Down Expand Up @@ -548,7 +561,15 @@ public void EncodeMeshDataIntoAccessors(ModelBaseFormat format)
}
}

public void ExportTextures(int imageFormat)
public void EncodeAnimationAccessors(ModelBaseFormat format)
{
for (int i = 0; i < animations.Count; i++)
{
animations[i].EncodeZeroTimeAnimationAccessors(this, format);
}
}

public void EncodeTextures(int imageFormat)
{
for (int i = 0; i < materials.Count; i++)
{
Expand All @@ -563,12 +584,14 @@ public void ExportTextures(int imageFormat)
}
}

public void EncodeAnimationAccessors(ModelBaseFormat format)
public void EncodeThumbnail(Texture2D thumbnailTexture, int imageFormat)
{
for (int i = 0; i < animations.Count; i++)
if (thumbnailTexture == null)
{
animations[i].EncodeZeroTimeAnimationAccessors(this, format);
Debug.LogError("No thumbnail texture provided for export.");
return;
}
thumbnailTextureAndImageIndex = ModelTexture.FromUnityTexture2D(this, thumbnailTexture, "thumbnail", imageFormat);
}

public void ExportToG3MF(string path)
Expand All @@ -583,9 +606,19 @@ public void ExportToG3MF(string path)
json.Append("\"KHR_character\",\"KHR_character_expression\",\"KHR_character_skeleton_mapping\"");
json.Append(",\"KHR_materials_unlit\",\"KHR_texture_transform\",\"KHR_xmp_json_ld\"");
// VRM extension declarations.
json.Append(",\"VRMC_materials_mtoon\",\"VRMC_springBone\",\"VRMC_vrm\"");
json.Append("]"); // End extensionsUsed
json.Append(",\"generator\":\"Yinglet Creator\",\"specification\":\"https://github.com/godot-dimensions/g4mf\"}");
//json.Append(",\"VRMC_materials_mtoon\"");
json.Append(",\"VRMC_springBone\",\"VRMC_vrm\"");
json.Append("]"); // End extensionsUsed.
json.Append(",\"generator\":\"");
json.Append(GetGeneratorText());
json.Append("\",\"specification\":\"https://github.com/godot-dimensions/g4mf\"");
if (thumbnailTextureAndImageIndex >= 0)
{
json.Append(",\"thumbnail\":");
json.Append(thumbnailTextureAndImageIndex);
}
// End asset header.
json.Append("}");
// Accessors.
ExportCollectionToJSON(json, "accessors", accessors, ModelBaseFormat.G3MF);
// Animations.
Expand Down Expand Up @@ -675,7 +708,9 @@ public void ExportToGLTF(string path, int vrmVersion = 1)
json.Append("{\"asset\":{");
json.Append("\"copyright\":\"" + System.DateTime.UtcNow.ToString("yyyy") + " Yinglet Creator\"");
json.Append(",\"extensions\":{\"KHR_xmp_json_ld\":{\"packet\":0}}"); // Packet applies to asset header / entire file.
json.Append(",\"generator\":\"Yinglet Creator\",\"version\":\"2.0\"}");
json.Append(",\"generator\":\"");
json.Append(GetGeneratorText());
json.Append("\",\"version\":\"2.0\"}");
// Accessors.
ExportCollectionToJSON(json, "accessors", accessors, ModelBaseFormat.GLTF);
// Animations.
Expand Down Expand Up @@ -727,7 +762,8 @@ public void ExportToGLTF(string path, int vrmVersion = 1)
}
else
{
json.Append(",\"VRMC_materials_mtoon\",\"VRMC_springBone\",\"VRMC_vrm\"");
//json.Append(",\"VRMC_materials_mtoon\"");
json.Append(",\"VRMC_springBone\",\"VRMC_vrm\"");
}
json.Append("]"); // End extensionsUsed
// Encode images of textures.
Expand Down Expand Up @@ -906,8 +942,19 @@ private void ExportKhrCharacterSkeletonMappingExtension(StringBuilder json)
private void ExportVRM0xTopLevelExtensions(StringBuilder json)
{
json.Append("\"VRM\":{");
json.Append("\"exporterVersion\":\"Yinglet Creator\"");
json.Append(",\"humanoid\":{");
json.Append("\"exporterVersion\":\"");
json.Append(GetGeneratorText());
json.Append("\"");
json.Append(",\"firstPerson\":{");
{
// Hard-coded values designed to work well with the exported Yinglet eye bones.
json.Append("\"lookAtHorizontalInner\":{\"xRange\":50.0,\"yRange\":3.0},");
json.Append("\"lookAtHorizontalOuter\":{\"xRange\":50.0,\"yRange\":6.0},");
json.Append("\"lookAtTypeName\":\"Bone\",");
json.Append("\"lookAtVerticalDown\":{\"xRange\":40.0,\"yRange\":3.0},");
json.Append("\"lookAtVerticalUp\":{\"xRange\":40.0,\"yRange\":3.0}");
}
json.Append("},\"humanoid\":{");
{
json.Append("\"humanBones\":[");
int vrmHumanBoneIndex = 0;
Expand Down Expand Up @@ -946,6 +993,12 @@ private void ExportVRM0xTopLevelExtensions(StringBuilder json)
json.Append("\"licenseName\":\"CC_BY_NC_SA\",");
json.Append("\"otherLicenseUrl\":\"https://github.com/TBartl/YingletCreator/blob/master/LICENSE\",");
json.Append("\"sexualUssageName\":\"Allow\",");
if (thumbnailTextureAndImageIndex >= 0)
{
json.Append("\"texture\":"); // VRM 0.x has the dumbest names I swear.
json.Append(thumbnailTextureAndImageIndex);
json.Append(",");
}
json.Append("\"title\":\"");
json.Append(nodes[0].name);
json.Append("\",");
Expand Down Expand Up @@ -985,7 +1038,23 @@ private void ExportVRM10TopLevelExtensions(StringBuilder json)
}
json.Append("},\"VRMC_vrm\":{");
{
json.Append("\"humanoid\":{");
json.Append("\"expressions\":{");
{
json.Append("\"custom\":{");
ModelAnimation winceAnim = FindAnimationByName("EyesWince");
AnimationToVRM10ExpressionJSON(json, winceAnim, "wince");
json.Append("},\"preset\":{");
int expressionIndex = 0;
foreach (var kvp in vrmExpressionToAnimationName)
{
ModelAnimation animation = FindAnimationByName(kvp.Value);
if (expressionIndex > 0) json.Append(",");
AnimationToVRM10ExpressionJSON(json, animation, kvp.Key);
expressionIndex++;
}
json.Append("}");
}
json.Append("},\"humanoid\":{");
{
json.Append("\"humanBones\":{");
int vrmHumanBoneIndex = 0;
Expand All @@ -999,6 +1068,15 @@ private void ExportVRM10TopLevelExtensions(StringBuilder json)
}
json.Append("}");
}
json.Append("},\"lookAt\":{");
{
// Hard-coded values designed to work well with the exported Yinglet eye bones.
json.Append("\"rangeMapHorizontalInner\":{\"inputMaxValue\":50.0,\"outputScale\":3.0},");
json.Append("\"rangeMapHorizontalOuter\":{\"inputMaxValue\":50.0,\"outputScale\":6.0},");
json.Append("\"rangeMapVerticalDown\":{\"inputMaxValue\":40.0,\"outputScale\":3.0},");
json.Append("\"rangeMapVerticalUp\":{\"inputMaxValue\":40.0,\"outputScale\":3.0},");
json.Append("\"type\":\"bone\"");
}
json.Append("},\"meta\":{");
{
json.Append("\"allowAntisocialOrHateUsage\":true,");
Expand All @@ -1017,6 +1095,12 @@ private void ExportVRM10TopLevelExtensions(StringBuilder json)
json.Append(nodes[0].name);
json.Append("\",");
json.Append("\"otherLicenseUrl\":\"https://github.com/TBartl/YingletCreator/blob/master/LICENSE\",");
if (thumbnailTextureAndImageIndex >= 0)
{
json.Append("\"thumbnailImage\":");
json.Append(thumbnailTextureAndImageIndex);
json.Append(",");
}
json.Append("\"version\":\"0.0\"");
}
json.Append("},");
Expand All @@ -1025,6 +1109,22 @@ private void ExportVRM10TopLevelExtensions(StringBuilder json)
json.Append("}");
}

private void AnimationToVRM10ExpressionJSON(StringBuilder json, ModelAnimation animation, string vrmName)
{
json.Append("\"");
json.Append(vrmName);
json.Append("\":{\"isBinary\":true,\"textureTransformBinds\":[{");
json.Append("\"material\":");
json.Append(animation.targetMaterialIndex);
json.Append(",\"offset\":[");
json.Append(ModelItem.Flt(animation.offset.x));
json.Append(",");
json.Append(ModelItem.Flt(animation.offset.y));
json.Append("]"); // Close offset
json.Append(",\"scale\":[1.0,1.0]"); // HACK: UniVRM crashes when scale is missing, even though it's optional in the spec.
json.Append("}]}"); // Close textureTransformBinds, close expression.
}

private void ExportCollectionToJSON<T>(StringBuilder json, string name, List<T> items, ModelBaseFormat baseFormat) where T : ModelItem
{
json.Append(",\"" + name + "\":[");
Expand Down Expand Up @@ -1064,4 +1164,23 @@ private int EncodeInverseBindMatrices()
}
return ModelAccessor.EncodeMatrix4x4s(this, inverseBindMatrices);
}

private static string GetGeneratorText()
{
return "Yinglet Creator " + Application.version;
}

/// <summary>
/// VRM 1.0's expression names have a decent mapping for everything except "EyesWince".
/// </summary>
private static readonly Dictionary<string, string> vrmExpressionToAnimationName = new Dictionary<string, string>
{
{"angry", "EyesAngry"},
{"blink", "EyesBlink"},
{"happy", "EyesPleased"},
{"neutral", "EyesNeutral"},
{"relaxed", "EyesSquint"},
{"sad", "EyesSad"},
{"surprised", "EyesShocked"},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ public override string ModelItemToJSON(ModelBaseFormat format)
json.Append("\"alphaMode\":\"MASK\",");
json.Append("\"extensions\":{");
json.Append("\"KHR_materials_unlit\":{}");
json.Append(",\"VRMC_materials_mtoon\":{\"specVersion\":\"1.0\"}");
//json.Append(",\"VRMC_materials_mtoon\":{\"specVersion\":\"1.0\"}");
json.Append("}"); // End extensions
json.Append(",\"name\":\"" + name + "\"");
json.Append(",\"pbrMetallicRoughness\":{\"baseColorTexture\":{");
Expand All @@ -232,7 +232,7 @@ public override string ModelItemToJSON(ModelBaseFormat format)
json.Append("}"); // End baseColor
json.Append(",\"extensions\":{");
json.Append("\"KHR_materials_unlit\":{}");
json.Append(",\"VRMC_materials_mtoon\":{\"specVersion\":\"1.0\"}");
//json.Append(",\"VRMC_materials_mtoon\":{\"specVersion\":\"1.0\"}");
json.Append("}"); // End extensions
json.Append(",\"name\":\"" + name + "\"");
}
Expand Down
Loading