mirror of
https://github.com/lubosz/overte.git
synced 2025-08-08 03:27:48 +02:00
Merge pull request #15115 from dback2/avatarExporterMaterials
Case 21226: Avatar Exporter v0.3.1 - export Unity material data to fst
This commit is contained in:
commit
703e1bda2f
3 changed files with 301 additions and 117 deletions
|
@ -14,12 +14,14 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
class AvatarExporter : MonoBehaviour {
|
class AvatarExporter : MonoBehaviour {
|
||||||
// update version number for every PR that changes this file, also set updated version in README file
|
// update version number for every PR that changes this file, also set updated version in README file
|
||||||
static readonly string AVATAR_EXPORTER_VERSION = "0.3";
|
static readonly string AVATAR_EXPORTER_VERSION = "0.3.1";
|
||||||
|
|
||||||
static readonly float HIPS_GROUND_MIN_Y = 0.01f;
|
static readonly float HIPS_GROUND_MIN_Y = 0.01f;
|
||||||
static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f;
|
static readonly float HIPS_SPINE_CHEST_MIN_SEPARATION = 0.001f;
|
||||||
static readonly int MAXIMUM_USER_BONE_COUNT = 256;
|
static readonly int MAXIMUM_USER_BONE_COUNT = 256;
|
||||||
static readonly string EMPTY_WARNING_TEXT = "None";
|
static readonly string EMPTY_WARNING_TEXT = "None";
|
||||||
|
static readonly string TEXTURES_DIRECTORY = "textures";
|
||||||
|
static readonly string DEFAULT_MATERIAL_NAME = "No Name";
|
||||||
|
|
||||||
// TODO: use regex
|
// TODO: use regex
|
||||||
static readonly string[] RECOMMENDED_UNITY_VERSIONS = new string[] {
|
static readonly string[] RECOMMENDED_UNITY_VERSIONS = new string[] {
|
||||||
|
@ -195,8 +197,17 @@ class AvatarExporter : MonoBehaviour {
|
||||||
" Thumb Intermediate",
|
" Thumb Intermediate",
|
||||||
" Thumb Proximal",
|
" Thumb Proximal",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static readonly string STANDARD_SHADER = "Standard";
|
||||||
|
static readonly string STANDARD_ROUGHNESS_SHADER = "Standard (Roughness setup)";
|
||||||
|
static readonly string STANDARD_SPECULAR_SHADER = "Standard (Specular setup)";
|
||||||
|
static readonly string[] SUPPORTED_SHADERS = new string[] {
|
||||||
|
STANDARD_SHADER,
|
||||||
|
STANDARD_ROUGHNESS_SHADER,
|
||||||
|
STANDARD_SPECULAR_SHADER,
|
||||||
|
};
|
||||||
|
|
||||||
enum BoneRule {
|
enum AvatarRule {
|
||||||
RecommendedUnityVersion,
|
RecommendedUnityVersion,
|
||||||
SingleRoot,
|
SingleRoot,
|
||||||
NoDuplicateMapping,
|
NoDuplicateMapping,
|
||||||
|
@ -215,14 +226,14 @@ class AvatarExporter : MonoBehaviour {
|
||||||
HipsNotOnGround,
|
HipsNotOnGround,
|
||||||
HipsSpineChestNotCoincident,
|
HipsSpineChestNotCoincident,
|
||||||
TotalBoneCountUnderLimit,
|
TotalBoneCountUnderLimit,
|
||||||
BoneRuleEnd,
|
AvatarRuleEnd,
|
||||||
};
|
};
|
||||||
// rules that are treated as errors and prevent exporting, otherwise rules will show as warnings
|
// rules that are treated as errors and prevent exporting, otherwise rules will show as warnings
|
||||||
static readonly BoneRule[] EXPORT_BLOCKING_BONE_RULES = new BoneRule[] {
|
static readonly AvatarRule[] EXPORT_BLOCKING_AVATAR_RULES = new AvatarRule[] {
|
||||||
BoneRule.HipsMapped,
|
AvatarRule.HipsMapped,
|
||||||
BoneRule.SpineMapped,
|
AvatarRule.SpineMapped,
|
||||||
BoneRule.ChestMapped,
|
AvatarRule.ChestMapped,
|
||||||
BoneRule.HeadMapped,
|
AvatarRule.HeadMapped,
|
||||||
};
|
};
|
||||||
|
|
||||||
class UserBoneInformation {
|
class UserBoneInformation {
|
||||||
|
@ -255,16 +266,64 @@ class AvatarExporter : MonoBehaviour {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Dictionary<string, UserBoneInformation> userBoneInfos = new Dictionary<string, UserBoneInformation>();
|
class MaterialData {
|
||||||
static Dictionary<string, string> humanoidToUserBoneMappings = new Dictionary<string, string>();
|
public Color albedo;
|
||||||
static BoneTreeNode userBoneTree = new BoneTreeNode();
|
public string albedoMap;
|
||||||
static Dictionary<BoneRule, string> failedBoneRules = new Dictionary<BoneRule, string>();
|
public double metallic;
|
||||||
|
public string metallicMap;
|
||||||
|
public double roughness;
|
||||||
|
public string roughnessMap;
|
||||||
|
public string normalMap;
|
||||||
|
public string occlusionMap;
|
||||||
|
public Color emissive;
|
||||||
|
public string emissiveMap;
|
||||||
|
|
||||||
|
public string getJSON() {
|
||||||
|
string json = "{ \"materialVersion\": 1, \"materials\": { ";
|
||||||
|
json += "\"albedo\": [" + albedo.r + ", " + albedo.g + ", " + albedo.b + "], ";
|
||||||
|
if (!string.IsNullOrEmpty(albedoMap)) {
|
||||||
|
json += "\"albedoMap\": \"" + albedoMap + "\", ";
|
||||||
|
}
|
||||||
|
json += "\"metallic\": " + metallic + ", ";
|
||||||
|
if (!string.IsNullOrEmpty(metallicMap)) {
|
||||||
|
json += "\"metallicMap\": \"" + metallicMap + "\", ";
|
||||||
|
}
|
||||||
|
json += "\"roughness\": " + roughness + ", ";
|
||||||
|
if (!string.IsNullOrEmpty(roughnessMap)) {
|
||||||
|
json += "\"roughnessMap\": \"" + roughnessMap + "\", ";
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(normalMap)) {
|
||||||
|
json += "\"normalMap\": \"" + normalMap + "\", ";
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrEmpty(occlusionMap)) {
|
||||||
|
json += "\"occlusionMap\": \"" + occlusionMap + "\", ";
|
||||||
|
}
|
||||||
|
json += "\"emissive\": [" + emissive.r + ", " + emissive.g + ", " + emissive.b + "] ";
|
||||||
|
if (!string.IsNullOrEmpty(emissiveMap)) {
|
||||||
|
json += "\", emissiveMap\": \"" + emissiveMap + "\"";
|
||||||
|
}
|
||||||
|
json += "} }";
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static string assetPath = "";
|
static string assetPath = "";
|
||||||
static string assetName = "";
|
static string assetName = "";
|
||||||
static HumanDescription humanDescription;
|
|
||||||
static Dictionary<string, string> dependencyTextures = new Dictionary<string, string>();
|
|
||||||
|
|
||||||
|
static ModelImporter modelImporter;
|
||||||
|
static HumanDescription humanDescription;
|
||||||
|
|
||||||
|
static Dictionary<string, UserBoneInformation> userBoneInfos = new Dictionary<string, UserBoneInformation>();
|
||||||
|
static Dictionary<string, string> humanoidToUserBoneMappings = new Dictionary<string, string>();
|
||||||
|
static BoneTreeNode userBoneTree = new BoneTreeNode();
|
||||||
|
static Dictionary<AvatarRule, string> failedAvatarRules = new Dictionary<AvatarRule, string>();
|
||||||
|
|
||||||
|
static Dictionary<string, string> textureDependencies = new Dictionary<string, string>();
|
||||||
|
static Dictionary<string, string> materialMappings = new Dictionary<string, string>();
|
||||||
|
static Dictionary<string, MaterialData> materialDatas = new Dictionary<string, MaterialData>();
|
||||||
|
static List<string> materialAlternateStandardShader = new List<string>();
|
||||||
|
static Dictionary<string, string> materialUnsupportedShader = new Dictionary<string, string>();
|
||||||
|
|
||||||
[MenuItem("High Fidelity/Export New Avatar")]
|
[MenuItem("High Fidelity/Export New Avatar")]
|
||||||
static void ExportNewAvatar() {
|
static void ExportNewAvatar() {
|
||||||
ExportSelectedAvatar(false);
|
ExportSelectedAvatar(false);
|
||||||
|
@ -280,7 +339,10 @@ class AvatarExporter : MonoBehaviour {
|
||||||
EditorUtility.DisplayDialog("About", "High Fidelity, Inc.\nAvatar Exporter\nVersion " + AVATAR_EXPORTER_VERSION, "Ok");
|
EditorUtility.DisplayDialog("About", "High Fidelity, Inc.\nAvatar Exporter\nVersion " + AVATAR_EXPORTER_VERSION, "Ok");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ExportSelectedAvatar(bool updateAvatar) {
|
static void ExportSelectedAvatar(bool updateAvatar) {
|
||||||
|
// ensure everything is saved to file before exporting
|
||||||
|
AssetDatabase.SaveAssets();
|
||||||
|
|
||||||
string[] guids = Selection.assetGUIDs;
|
string[] guids = Selection.assetGUIDs;
|
||||||
if (guids.Length != 1) {
|
if (guids.Length != 1) {
|
||||||
if (guids.Length == 0) {
|
if (guids.Length == 0) {
|
||||||
|
@ -292,7 +354,7 @@ class AvatarExporter : MonoBehaviour {
|
||||||
}
|
}
|
||||||
assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]);
|
assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]);
|
||||||
assetName = Path.GetFileNameWithoutExtension(assetPath);
|
assetName = Path.GetFileNameWithoutExtension(assetPath);
|
||||||
ModelImporter modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter;
|
modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter;
|
||||||
if (Path.GetExtension(assetPath).ToLower() != ".fbx" || modelImporter == null) {
|
if (Path.GetExtension(assetPath).ToLower() != ".fbx" || modelImporter == null) {
|
||||||
EditorUtility.DisplayDialog("Error", "Please select an .fbx model asset to export.", "Ok");
|
EditorUtility.DisplayDialog("Error", "Please select an .fbx model asset to export.", "Ok");
|
||||||
return;
|
return;
|
||||||
|
@ -304,24 +366,32 @@ class AvatarExporter : MonoBehaviour {
|
||||||
}
|
}
|
||||||
|
|
||||||
humanDescription = modelImporter.humanDescription;
|
humanDescription = modelImporter.humanDescription;
|
||||||
SetUserBoneInformation();
|
|
||||||
string textureWarnings = SetTextureDependencies();
|
string textureWarnings = SetTextureDependencies();
|
||||||
|
SetBoneAndMaterialInformation();
|
||||||
|
|
||||||
// check if we should be substituting a bone for a missing UpperChest mapping
|
// check if we should be substituting a bone for a missing UpperChest mapping
|
||||||
AdjustUpperChestMapping();
|
AdjustUpperChestMapping();
|
||||||
|
|
||||||
// format resulting bone rule failure strings
|
// format resulting avatar rule failure strings
|
||||||
// consider export-blocking bone rules to be errors and show them in an error dialog,
|
// consider export-blocking avatar rules to be errors and show them in an error dialog,
|
||||||
// and also include any other bone rule failures plus texture warnings as warnings in the dialog
|
// and also include any other avatar rule failures plus texture warnings as warnings in the dialog
|
||||||
string boneErrors = "";
|
string boneErrors = "";
|
||||||
string warnings = "";
|
string warnings = "";
|
||||||
foreach (var failedBoneRule in failedBoneRules) {
|
foreach (var failedAvatarRule in failedAvatarRules) {
|
||||||
if (Array.IndexOf(EXPORT_BLOCKING_BONE_RULES, failedBoneRule.Key) >= 0) {
|
if (Array.IndexOf(EXPORT_BLOCKING_AVATAR_RULES, failedAvatarRule.Key) >= 0) {
|
||||||
boneErrors += failedBoneRule.Value + "\n\n";
|
boneErrors += failedAvatarRule.Value + "\n\n";
|
||||||
} else {
|
} else {
|
||||||
warnings += failedBoneRule.Value + "\n\n";
|
warnings += failedAvatarRule.Value + "\n\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
foreach (string materialName in materialAlternateStandardShader) {
|
||||||
|
warnings += "The material " + materialName + " is not using the recommended variation of the Standard shader. " +
|
||||||
|
"We recommend you change it to Standard (Roughness setup) shader for improved performance.\n\n";
|
||||||
|
}
|
||||||
|
foreach (var material in materialUnsupportedShader) {
|
||||||
|
warnings += "The material " + material.Key + " is using an unsupported shader " + material.Value +
|
||||||
|
". Please change it to a Standard shader type.\n\n";
|
||||||
|
}
|
||||||
warnings += textureWarnings;
|
warnings += textureWarnings;
|
||||||
if (!string.IsNullOrEmpty(boneErrors)) {
|
if (!string.IsNullOrEmpty(boneErrors)) {
|
||||||
// if there are both errors and warnings then warnings will be displayed with errors in the error dialog
|
// if there are both errors and warnings then warnings will be displayed with errors in the error dialog
|
||||||
|
@ -408,9 +478,9 @@ class AvatarExporter : MonoBehaviour {
|
||||||
modelImporter.SaveAndReimport();
|
modelImporter.SaveAndReimport();
|
||||||
|
|
||||||
// redo parent names, joint mappings, and user bone positions due to the fbx change
|
// redo parent names, joint mappings, and user bone positions due to the fbx change
|
||||||
// as well as re-check the bone rules for failures
|
// as well as re-check the avatar rules for failures
|
||||||
humanDescription = modelImporter.humanDescription;
|
humanDescription = modelImporter.humanDescription;
|
||||||
SetUserBoneInformation();
|
SetBoneAndMaterialInformation();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -456,7 +526,7 @@ class AvatarExporter : MonoBehaviour {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// display success dialog with any bone rule warnings
|
// display success dialog with any avatar rule warnings
|
||||||
string successDialog = "Avatar successfully updated!";
|
string successDialog = "Avatar successfully updated!";
|
||||||
if (!string.IsNullOrEmpty(warnings)) {
|
if (!string.IsNullOrEmpty(warnings)) {
|
||||||
successDialog += "\n\nWarnings:\n" + warnings;
|
successDialog += "\n\nWarnings:\n" + warnings;
|
||||||
|
@ -575,6 +645,27 @@ class AvatarExporter : MonoBehaviour {
|
||||||
jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n");
|
jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if there is any material data to save then write out all materials in JSON material format to the materialMap field
|
||||||
|
if (materialDatas.Count > 0) {
|
||||||
|
string materialJson = "{ ";
|
||||||
|
foreach (var materialData in materialDatas) {
|
||||||
|
// if this is the only material in the mapping and it is the default name No Name mapped to No Name,
|
||||||
|
// then the avatar has no embedded materials and this material should be applied to all meshes
|
||||||
|
string materialName = materialData.Key;
|
||||||
|
if (materialMappings.Count == 1 && materialName == DEFAULT_MATERIAL_NAME &&
|
||||||
|
materialMappings[materialName] == DEFAULT_MATERIAL_NAME) {
|
||||||
|
materialJson += "\"all\": ";
|
||||||
|
} else {
|
||||||
|
materialJson += "\"mat::" + materialName + "\": ";
|
||||||
|
}
|
||||||
|
materialJson += materialData.Value.getJSON();
|
||||||
|
materialJson += ", ";
|
||||||
|
}
|
||||||
|
materialJson = materialJson.Substring(0, materialJson.LastIndexOf(", "));
|
||||||
|
materialJson += " }";
|
||||||
|
File.AppendAllText(exportFstPath, "materialMap = " + materialJson);
|
||||||
|
}
|
||||||
|
|
||||||
// open File Explorer to the project directory once finished
|
// open File Explorer to the project directory once finished
|
||||||
System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath);
|
System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath);
|
||||||
|
@ -582,11 +673,17 @@ class AvatarExporter : MonoBehaviour {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void SetUserBoneInformation() {
|
static void SetBoneAndMaterialInformation() {
|
||||||
userBoneInfos.Clear();
|
userBoneInfos.Clear();
|
||||||
humanoidToUserBoneMappings.Clear();
|
humanoidToUserBoneMappings.Clear();
|
||||||
userBoneTree = new BoneTreeNode();
|
userBoneTree = new BoneTreeNode();
|
||||||
|
|
||||||
|
materialDatas.Clear();
|
||||||
|
materialAlternateStandardShader.Clear();
|
||||||
|
materialUnsupportedShader.Clear();
|
||||||
|
|
||||||
|
SetMaterialMappings();
|
||||||
|
|
||||||
// instantiate a game object of the user avatar to traverse the bone tree to gather
|
// instantiate a game object of the user avatar to traverse the bone tree to gather
|
||||||
// bone parents and positions as well as build a bone tree, then destroy it
|
// bone parents and positions as well as build a bone tree, then destroy it
|
||||||
UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object));
|
UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object));
|
||||||
|
@ -610,20 +707,26 @@ class AvatarExporter : MonoBehaviour {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate the list of bone rule failure strings for any bone rules that are not satisfied by this avatar
|
// generate the list of avatar rule failure strings for any avatar rules that are not satisfied by this avatar
|
||||||
SetFailedBoneRules();
|
SetFailedAvatarRules();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void TraverseUserBoneTree(Transform modelBone) {
|
static void TraverseUserBoneTree(Transform modelBone) {
|
||||||
GameObject gameObject = modelBone.gameObject;
|
GameObject gameObject = modelBone.gameObject;
|
||||||
|
|
||||||
// check if this transform is a node containing mesh, light, or camera instead of a bone
|
// check if this transform is a node containing mesh, light, or camera instead of a bone
|
||||||
bool mesh = gameObject.GetComponent<MeshRenderer>() != null || gameObject.GetComponent<SkinnedMeshRenderer>() != null;
|
MeshRenderer meshRenderer = gameObject.GetComponent<MeshRenderer>();
|
||||||
|
SkinnedMeshRenderer skinnedMeshRenderer = gameObject.GetComponent<SkinnedMeshRenderer>();
|
||||||
|
bool mesh = meshRenderer != null || skinnedMeshRenderer != null;
|
||||||
bool light = gameObject.GetComponent<Light>() != null;
|
bool light = gameObject.GetComponent<Light>() != null;
|
||||||
bool camera = gameObject.GetComponent<Camera>() != null;
|
bool camera = gameObject.GetComponent<Camera>() != null;
|
||||||
|
|
||||||
// if it is in fact a bone, add it to the bone tree as well as user bone infos list with position and parent name
|
// if this is a mesh and the model is using external materials then store its material data to be exported
|
||||||
if (!mesh && !light && !camera) {
|
if (mesh && modelImporter.materialLocation == ModelImporterMaterialLocation.External) {
|
||||||
|
Material[] materials = skinnedMeshRenderer != null ? skinnedMeshRenderer.sharedMaterials : meshRenderer.sharedMaterials;
|
||||||
|
StoreMaterialData(materials);
|
||||||
|
} else if (!light && !camera) {
|
||||||
|
// if it is in fact a bone, add it to the bone tree as well as user bone infos list with position and parent name
|
||||||
UserBoneInformation userBoneInfo = new UserBoneInformation();
|
UserBoneInformation userBoneInfo = new UserBoneInformation();
|
||||||
userBoneInfo.position = modelBone.position; // bone's absolute position
|
userBoneInfo.position = modelBone.position; // bone's absolute position
|
||||||
|
|
||||||
|
@ -692,8 +795,8 @@ class AvatarExporter : MonoBehaviour {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
static void SetFailedBoneRules() {
|
static void SetFailedAvatarRules() {
|
||||||
failedBoneRules.Clear();
|
failedAvatarRules.Clear();
|
||||||
|
|
||||||
string hipsUserBone = "";
|
string hipsUserBone = "";
|
||||||
string spineUserBone = "";
|
string spineUserBone = "";
|
||||||
|
@ -702,60 +805,60 @@ class AvatarExporter : MonoBehaviour {
|
||||||
|
|
||||||
Vector3 hipsPosition = new Vector3();
|
Vector3 hipsPosition = new Vector3();
|
||||||
|
|
||||||
// iterate over all bone rules in order and add any rules that fail
|
// iterate over all avatar rules in order and add any rules that fail
|
||||||
// to the failed bone rules map with appropriate error or warning text
|
// to the failed avatar rules map with appropriate error or warning text
|
||||||
for (BoneRule boneRule = 0; boneRule < BoneRule.BoneRuleEnd; ++boneRule) {
|
for (AvatarRule avatarRule = 0; avatarRule < AvatarRule.AvatarRuleEnd; ++avatarRule) {
|
||||||
switch (boneRule) {
|
switch (avatarRule) {
|
||||||
case BoneRule.RecommendedUnityVersion:
|
case AvatarRule.RecommendedUnityVersion:
|
||||||
if (Array.IndexOf(RECOMMENDED_UNITY_VERSIONS, Application.unityVersion) == -1) {
|
if (Array.IndexOf(RECOMMENDED_UNITY_VERSIONS, Application.unityVersion) == -1) {
|
||||||
failedBoneRules.Add(boneRule, "The current version of Unity is not one of the recommended Unity " +
|
failedAvatarRules.Add(avatarRule, "The current version of Unity is not one of the recommended Unity " +
|
||||||
"versions. If you are using a version of Unity later than 2018.2.12f1, " +
|
"versions. If you are using a version of Unity later than 2018.2.12f1, " +
|
||||||
"it is recommended to apply Enforce T-Pose under the Pose dropdown " +
|
"it is recommended to apply Enforce T-Pose under the Pose dropdown " +
|
||||||
"in Humanoid configuration.");
|
"in Humanoid configuration.");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BoneRule.SingleRoot:
|
case AvatarRule.SingleRoot:
|
||||||
// bone rule fails if the root bone node has more than one child bone
|
// avatar rule fails if the root bone node has more than one child bone
|
||||||
if (userBoneTree.children.Count > 1) {
|
if (userBoneTree.children.Count > 1) {
|
||||||
failedBoneRules.Add(boneRule, "There is more than one bone at the top level of the selected avatar's " +
|
failedAvatarRules.Add(avatarRule, "There is more than one bone at the top level of the selected avatar's " +
|
||||||
"bone hierarchy. Please ensure all bones for Humanoid mappings are " +
|
"bone hierarchy. Please ensure all bones for Humanoid mappings are " +
|
||||||
"under the same bone hierarchy.");
|
"under the same bone hierarchy.");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BoneRule.NoDuplicateMapping:
|
case AvatarRule.NoDuplicateMapping:
|
||||||
// bone rule fails if any user bone is mapped to more than one Humanoid bone
|
// avatar rule fails if any user bone is mapped to more than one Humanoid bone
|
||||||
foreach (var userBoneInfo in userBoneInfos) {
|
foreach (var userBoneInfo in userBoneInfos) {
|
||||||
string boneName = userBoneInfo.Key;
|
string boneName = userBoneInfo.Key;
|
||||||
int mappingCount = userBoneInfo.Value.mappingCount;
|
int mappingCount = userBoneInfo.Value.mappingCount;
|
||||||
if (mappingCount > 1) {
|
if (mappingCount > 1) {
|
||||||
string text = "The " + boneName + " bone is mapped to more than one bone in Humanoid.";
|
string text = "The " + boneName + " bone is mapped to more than one bone in Humanoid.";
|
||||||
if (failedBoneRules.ContainsKey(boneRule)) {
|
if (failedAvatarRules.ContainsKey(avatarRule)) {
|
||||||
failedBoneRules[boneRule] += "\n" + text;
|
failedAvatarRules[avatarRule] += "\n" + text;
|
||||||
} else {
|
} else {
|
||||||
failedBoneRules.Add(boneRule, text);
|
failedAvatarRules.Add(avatarRule, text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BoneRule.NoAsymmetricalLegMapping:
|
case AvatarRule.NoAsymmetricalLegMapping:
|
||||||
CheckAsymmetricalMappingRule(boneRule, LEG_MAPPING_SUFFIXES, "leg");
|
CheckAsymmetricalMappingRule(avatarRule, LEG_MAPPING_SUFFIXES, "leg");
|
||||||
break;
|
break;
|
||||||
case BoneRule.NoAsymmetricalArmMapping:
|
case AvatarRule.NoAsymmetricalArmMapping:
|
||||||
CheckAsymmetricalMappingRule(boneRule, ARM_MAPPING_SUFFIXES, "arm");
|
CheckAsymmetricalMappingRule(avatarRule, ARM_MAPPING_SUFFIXES, "arm");
|
||||||
break;
|
break;
|
||||||
case BoneRule.NoAsymmetricalHandMapping:
|
case AvatarRule.NoAsymmetricalHandMapping:
|
||||||
CheckAsymmetricalMappingRule(boneRule, HAND_MAPPING_SUFFIXES, "hand");
|
CheckAsymmetricalMappingRule(avatarRule, HAND_MAPPING_SUFFIXES, "hand");
|
||||||
break;
|
break;
|
||||||
case BoneRule.HipsMapped:
|
case AvatarRule.HipsMapped:
|
||||||
hipsUserBone = CheckHumanBoneMappingRule(boneRule, "Hips");
|
hipsUserBone = CheckHumanBoneMappingRule(avatarRule, "Hips");
|
||||||
break;
|
break;
|
||||||
case BoneRule.SpineMapped:
|
case AvatarRule.SpineMapped:
|
||||||
spineUserBone = CheckHumanBoneMappingRule(boneRule, "Spine");
|
spineUserBone = CheckHumanBoneMappingRule(avatarRule, "Spine");
|
||||||
break;
|
break;
|
||||||
case BoneRule.SpineDescendantOfHips:
|
case AvatarRule.SpineDescendantOfHips:
|
||||||
CheckUserBoneDescendantOfHumanRule(boneRule, spineUserBone, "Hips");
|
CheckUserBoneDescendantOfHumanRule(avatarRule, spineUserBone, "Hips");
|
||||||
break;
|
break;
|
||||||
case BoneRule.ChestMapped:
|
case AvatarRule.ChestMapped:
|
||||||
if (!humanoidToUserBoneMappings.TryGetValue("Chest", out chestUserBone)) {
|
if (!humanoidToUserBoneMappings.TryGetValue("Chest", out chestUserBone)) {
|
||||||
// check to see if there is a child of Spine that we can suggest to be mapped to Chest
|
// check to see if there is a child of Spine that we can suggest to be mapped to Chest
|
||||||
string spineChild = "";
|
string spineChild = "";
|
||||||
|
@ -765,54 +868,54 @@ class AvatarExporter : MonoBehaviour {
|
||||||
spineChild = spineTreeNode.children[0].boneName;
|
spineChild = spineTreeNode.children[0].boneName;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
failedBoneRules.Add(boneRule, "There is no Chest bone mapped in Humanoid for the selected avatar.");
|
failedAvatarRules.Add(avatarRule, "There is no Chest bone mapped in Humanoid for the selected avatar.");
|
||||||
// if the only found child of Spine is not yet mapped then add it as a suggestion for Chest mapping
|
// if the only found child of Spine is not yet mapped then add it as a suggestion for Chest mapping
|
||||||
if (!string.IsNullOrEmpty(spineChild) && !userBoneInfos[spineChild].HasHumanMapping()) {
|
if (!string.IsNullOrEmpty(spineChild) && !userBoneInfos[spineChild].HasHumanMapping()) {
|
||||||
failedBoneRules[boneRule] += " It is suggested that you map bone " + spineChild +
|
failedAvatarRules[avatarRule] += " It is suggested that you map bone " + spineChild +
|
||||||
" to Chest in Humanoid.";
|
" to Chest in Humanoid.";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BoneRule.ChestDescendantOfSpine:
|
case AvatarRule.ChestDescendantOfSpine:
|
||||||
CheckUserBoneDescendantOfHumanRule(boneRule, chestUserBone, "Spine");
|
CheckUserBoneDescendantOfHumanRule(avatarRule, chestUserBone, "Spine");
|
||||||
break;
|
break;
|
||||||
case BoneRule.NeckMapped:
|
case AvatarRule.NeckMapped:
|
||||||
CheckHumanBoneMappingRule(boneRule, "Neck");
|
CheckHumanBoneMappingRule(avatarRule, "Neck");
|
||||||
break;
|
break;
|
||||||
case BoneRule.HeadMapped:
|
case AvatarRule.HeadMapped:
|
||||||
headUserBone = CheckHumanBoneMappingRule(boneRule, "Head");
|
headUserBone = CheckHumanBoneMappingRule(avatarRule, "Head");
|
||||||
break;
|
break;
|
||||||
case BoneRule.HeadDescendantOfChest:
|
case AvatarRule.HeadDescendantOfChest:
|
||||||
CheckUserBoneDescendantOfHumanRule(boneRule, headUserBone, "Chest");
|
CheckUserBoneDescendantOfHumanRule(avatarRule, headUserBone, "Chest");
|
||||||
break;
|
break;
|
||||||
case BoneRule.EyesMapped:
|
case AvatarRule.EyesMapped:
|
||||||
bool leftEyeMapped = humanoidToUserBoneMappings.ContainsKey("LeftEye");
|
bool leftEyeMapped = humanoidToUserBoneMappings.ContainsKey("LeftEye");
|
||||||
bool rightEyeMapped = humanoidToUserBoneMappings.ContainsKey("RightEye");
|
bool rightEyeMapped = humanoidToUserBoneMappings.ContainsKey("RightEye");
|
||||||
if (!leftEyeMapped || !rightEyeMapped) {
|
if (!leftEyeMapped || !rightEyeMapped) {
|
||||||
if (leftEyeMapped && !rightEyeMapped) {
|
if (leftEyeMapped && !rightEyeMapped) {
|
||||||
failedBoneRules.Add(boneRule, "There is no RightEye bone mapped in Humanoid " +
|
failedAvatarRules.Add(avatarRule, "There is no RightEye bone mapped in Humanoid " +
|
||||||
"for the selected avatar.");
|
"for the selected avatar.");
|
||||||
} else if (!leftEyeMapped && rightEyeMapped) {
|
} else if (!leftEyeMapped && rightEyeMapped) {
|
||||||
failedBoneRules.Add(boneRule, "There is no LeftEye bone mapped in Humanoid " +
|
failedAvatarRules.Add(avatarRule, "There is no LeftEye bone mapped in Humanoid " +
|
||||||
"for the selected avatar.");
|
"for the selected avatar.");
|
||||||
} else {
|
} else {
|
||||||
failedBoneRules.Add(boneRule, "There is no LeftEye or RightEye bone mapped in Humanoid " +
|
failedAvatarRules.Add(avatarRule, "There is no LeftEye or RightEye bone mapped in Humanoid " +
|
||||||
"for the selected avatar.");
|
"for the selected avatar.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BoneRule.HipsNotOnGround:
|
case AvatarRule.HipsNotOnGround:
|
||||||
// ensure the absolute Y position for the bone mapped to Hips (if its mapped) is at least HIPS_GROUND_MIN_Y
|
// ensure the absolute Y position for the bone mapped to Hips (if its mapped) is at least HIPS_GROUND_MIN_Y
|
||||||
if (!string.IsNullOrEmpty(hipsUserBone)) {
|
if (!string.IsNullOrEmpty(hipsUserBone)) {
|
||||||
UserBoneInformation hipsBoneInfo = userBoneInfos[hipsUserBone];
|
UserBoneInformation hipsBoneInfo = userBoneInfos[hipsUserBone];
|
||||||
hipsPosition = hipsBoneInfo.position;
|
hipsPosition = hipsBoneInfo.position;
|
||||||
if (hipsPosition.y < HIPS_GROUND_MIN_Y) {
|
if (hipsPosition.y < HIPS_GROUND_MIN_Y) {
|
||||||
failedBoneRules.Add(boneRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone +
|
failedAvatarRules.Add(avatarRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone +
|
||||||
") should not be at ground level.");
|
") should not be at ground level.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BoneRule.HipsSpineChestNotCoincident:
|
case AvatarRule.HipsSpineChestNotCoincident:
|
||||||
// ensure the bones mapped to Hips, Spine, and Chest are all not in the same position,
|
// ensure the bones mapped to Hips, Spine, and Chest are all not in the same position,
|
||||||
// check Hips to Spine and Spine to Chest lengths are within HIPS_SPINE_CHEST_MIN_SEPARATION
|
// check Hips to Spine and Spine to Chest lengths are within HIPS_SPINE_CHEST_MIN_SEPARATION
|
||||||
if (!string.IsNullOrEmpty(spineUserBone) && !string.IsNullOrEmpty(chestUserBone) &&
|
if (!string.IsNullOrEmpty(spineUserBone) && !string.IsNullOrEmpty(chestUserBone) &&
|
||||||
|
@ -823,34 +926,34 @@ class AvatarExporter : MonoBehaviour {
|
||||||
Vector3 spineToChest = spineBoneInfo.position - chestBoneInfo.position;
|
Vector3 spineToChest = spineBoneInfo.position - chestBoneInfo.position;
|
||||||
if (hipsToSpine.magnitude < HIPS_SPINE_CHEST_MIN_SEPARATION &&
|
if (hipsToSpine.magnitude < HIPS_SPINE_CHEST_MIN_SEPARATION &&
|
||||||
spineToChest.magnitude < HIPS_SPINE_CHEST_MIN_SEPARATION) {
|
spineToChest.magnitude < HIPS_SPINE_CHEST_MIN_SEPARATION) {
|
||||||
failedBoneRules.Add(boneRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone +
|
failedAvatarRules.Add(avatarRule, "The bone mapped to Hips in Humanoid (" + hipsUserBone +
|
||||||
"), the bone mapped to Spine in Humanoid (" + spineUserBone +
|
"), the bone mapped to Spine in Humanoid (" + spineUserBone +
|
||||||
"), and the bone mapped to Chest in Humanoid (" + chestUserBone +
|
"), and the bone mapped to Chest in Humanoid (" + chestUserBone +
|
||||||
") should not be coincidental.");
|
") should not be coincidental.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BoneRule.TotalBoneCountUnderLimit:
|
case AvatarRule.TotalBoneCountUnderLimit:
|
||||||
int userBoneCount = userBoneInfos.Count;
|
int userBoneCount = userBoneInfos.Count;
|
||||||
if (userBoneCount > MAXIMUM_USER_BONE_COUNT) {
|
if (userBoneCount > MAXIMUM_USER_BONE_COUNT) {
|
||||||
failedBoneRules.Add(boneRule, "The total number of bones in the avatar (" + userBoneCount +
|
failedAvatarRules.Add(avatarRule, "The total number of bones in the avatar (" + userBoneCount +
|
||||||
") exceeds the maximum bone limit (" + MAXIMUM_USER_BONE_COUNT + ").");
|
") exceeds the maximum bone limit (" + MAXIMUM_USER_BONE_COUNT + ").");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static string CheckHumanBoneMappingRule(BoneRule boneRule, string humanBoneName) {
|
static string CheckHumanBoneMappingRule(AvatarRule avatarRule, string humanBoneName) {
|
||||||
string userBoneName = "";
|
string userBoneName = "";
|
||||||
// bone rule fails if bone is not mapped in Humanoid
|
// avatar rule fails if bone is not mapped in Humanoid
|
||||||
if (!humanoidToUserBoneMappings.TryGetValue(humanBoneName, out userBoneName)) {
|
if (!humanoidToUserBoneMappings.TryGetValue(humanBoneName, out userBoneName)) {
|
||||||
failedBoneRules.Add(boneRule, "There is no " + humanBoneName + " bone mapped in Humanoid for the selected avatar.");
|
failedAvatarRules.Add(avatarRule, "There is no " + humanBoneName + " bone mapped in Humanoid for the selected avatar.");
|
||||||
}
|
}
|
||||||
return userBoneName;
|
return userBoneName;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CheckUserBoneDescendantOfHumanRule(BoneRule boneRule, string userBoneName, string descendantOfHumanName) {
|
static void CheckUserBoneDescendantOfHumanRule(AvatarRule avatarRule, string userBoneName, string descendantOfHumanName) {
|
||||||
if (string.IsNullOrEmpty(userBoneName)) {
|
if (string.IsNullOrEmpty(userBoneName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -877,13 +980,13 @@ class AvatarExporter : MonoBehaviour {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// bone rule fails if no ancestor of given user bone matched the descendant of name (no early return)
|
// avatar rule fails if no ancestor of given user bone matched the descendant of name (no early return)
|
||||||
failedBoneRules.Add(boneRule, "The bone mapped to " + userBoneInfo.humanName + " in Humanoid (" + userBoneName +
|
failedAvatarRules.Add(avatarRule, "The bone mapped to " + userBoneInfo.humanName + " in Humanoid (" + userBoneName +
|
||||||
") is not a child of the bone mapped to " + descendantOfHumanName + " in Humanoid (" +
|
") is not a child of the bone mapped to " + descendantOfHumanName + " in Humanoid (" +
|
||||||
descendantOfUserBoneName + ").");
|
descendantOfUserBoneName + ").");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CheckAsymmetricalMappingRule(BoneRule boneRule, string[] mappingSuffixes, string appendage) {
|
static void CheckAsymmetricalMappingRule(AvatarRule avatarRule, string[] mappingSuffixes, string appendage) {
|
||||||
int leftCount = 0;
|
int leftCount = 0;
|
||||||
int rightCount = 0;
|
int rightCount = 0;
|
||||||
// add Left/Right to each mapping suffix to make Humanoid mapping names,
|
// add Left/Right to each mapping suffix to make Humanoid mapping names,
|
||||||
|
@ -898,23 +1001,23 @@ class AvatarExporter : MonoBehaviour {
|
||||||
++rightCount;
|
++rightCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// bone rule fails if number of left appendage mappings doesn't match number of right appendage mappings
|
// avatar rule fails if number of left appendage mappings doesn't match number of right appendage mappings
|
||||||
if (leftCount != rightCount) {
|
if (leftCount != rightCount) {
|
||||||
failedBoneRules.Add(boneRule, "The number of bones mapped in Humanoid for the left " + appendage + " (" +
|
failedAvatarRules.Add(avatarRule, "The number of bones mapped in Humanoid for the left " + appendage + " (" +
|
||||||
leftCount + ") does not match the number of bones mapped in Humanoid for the right " +
|
leftCount + ") does not match the number of bones mapped in Humanoid for the right " +
|
||||||
appendage + " (" + rightCount + ").");
|
appendage + " (" + rightCount + ").");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static string GetTextureDirectory(string basePath) {
|
static string GetTextureDirectory(string basePath) {
|
||||||
string textureDirectory = Path.GetDirectoryName(basePath) + "\\textures";
|
string textureDirectory = Path.GetDirectoryName(basePath) + "\\" + TEXTURES_DIRECTORY;
|
||||||
textureDirectory = textureDirectory.Replace("\\\\", "\\");
|
textureDirectory = textureDirectory.Replace("\\\\", "\\");
|
||||||
return textureDirectory;
|
return textureDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
static string SetTextureDependencies() {
|
static string SetTextureDependencies() {
|
||||||
string textureWarnings = "";
|
string textureWarnings = "";
|
||||||
dependencyTextures.Clear();
|
textureDependencies.Clear();
|
||||||
|
|
||||||
// build the list of all local asset paths for textures that Unity considers dependencies of the model
|
// build the list of all local asset paths for textures that Unity considers dependencies of the model
|
||||||
// for any textures that have duplicate names, return a string of duplicate name warnings
|
// for any textures that have duplicate names, return a string of duplicate name warnings
|
||||||
|
@ -923,11 +1026,11 @@ class AvatarExporter : MonoBehaviour {
|
||||||
UnityEngine.Object textureObject = AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(Texture2D));
|
UnityEngine.Object textureObject = AssetDatabase.LoadAssetAtPath(dependencyPath, typeof(Texture2D));
|
||||||
if (textureObject != null) {
|
if (textureObject != null) {
|
||||||
string textureName = Path.GetFileName(dependencyPath);
|
string textureName = Path.GetFileName(dependencyPath);
|
||||||
if (dependencyTextures.ContainsKey(textureName)) {
|
if (textureDependencies.ContainsKey(textureName)) {
|
||||||
textureWarnings += "There is more than one texture with the name " + textureName +
|
textureWarnings += "There is more than one texture with the name " + textureName +
|
||||||
" referenced in the selected avatar.\n\n";
|
" referenced in the selected avatar.\n\n";
|
||||||
} else {
|
} else {
|
||||||
dependencyTextures.Add(textureName, dependencyPath);
|
textureDependencies.Add(textureName, dependencyPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -937,7 +1040,7 @@ class AvatarExporter : MonoBehaviour {
|
||||||
|
|
||||||
static bool CopyExternalTextures(string texturesDirectory) {
|
static bool CopyExternalTextures(string texturesDirectory) {
|
||||||
// copy the found dependency textures from the local asset folder to the textures folder in the target export project
|
// copy the found dependency textures from the local asset folder to the textures folder in the target export project
|
||||||
foreach (var texture in dependencyTextures) {
|
foreach (var texture in textureDependencies) {
|
||||||
string targetPath = texturesDirectory + "\\" + texture.Key;
|
string targetPath = texturesDirectory + "\\" + texture.Key;
|
||||||
try {
|
try {
|
||||||
File.Copy(texture.Value, targetPath, true);
|
File.Copy(texture.Value, targetPath, true);
|
||||||
|
@ -949,6 +1052,88 @@ class AvatarExporter : MonoBehaviour {
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void StoreMaterialData(Material[] materials) {
|
||||||
|
// store each material's info in the materialDatas list to be written out later to the FST if it is a supported shader
|
||||||
|
foreach (Material material in materials) {
|
||||||
|
string materialName = material.name;
|
||||||
|
string shaderName = material.shader.name;
|
||||||
|
|
||||||
|
// don't store any material data for unsupported shader types
|
||||||
|
if (Array.IndexOf(SUPPORTED_SHADERS, shaderName) == -1) {
|
||||||
|
if (!materialUnsupportedShader.ContainsKey(materialName)) {
|
||||||
|
materialUnsupportedShader.Add(materialName, shaderName);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialData materialData = new MaterialData();
|
||||||
|
materialData.albedo = material.GetColor("_Color");
|
||||||
|
materialData.albedoMap = GetMaterialTexture(material, "_MainTex");
|
||||||
|
materialData.roughness = material.GetFloat("_Glossiness");
|
||||||
|
materialData.roughnessMap = GetMaterialTexture(material, "_SpecGlossMap");
|
||||||
|
materialData.normalMap = GetMaterialTexture(material, "_BumpMap");
|
||||||
|
materialData.occlusionMap = GetMaterialTexture(material, "_OcclusionMap");
|
||||||
|
materialData.emissive = material.GetColor("_EmissionColor");
|
||||||
|
materialData.emissiveMap = GetMaterialTexture(material, "_EmissionMap");
|
||||||
|
|
||||||
|
// for specular setups we will treat the metallic value as the average of the specular RGB intensities
|
||||||
|
if (shaderName == STANDARD_SPECULAR_SHADER) {
|
||||||
|
Color specular = material.GetColor("_SpecColor");
|
||||||
|
materialData.metallic = (specular.r + specular.g + specular.b) / 3.0f;
|
||||||
|
} else {
|
||||||
|
materialData.metallic = material.GetFloat("_Metallic");
|
||||||
|
materialData.metallicMap = GetMaterialTexture(material, "_MetallicGlossMap");
|
||||||
|
}
|
||||||
|
|
||||||
|
// for non-roughness Standard shaders give a warning that is not the recommended Standard shader,
|
||||||
|
// and invert smoothness for roughness
|
||||||
|
if (shaderName == STANDARD_SHADER || shaderName == STANDARD_SPECULAR_SHADER) {
|
||||||
|
if (!materialAlternateStandardShader.Contains(materialName)) {
|
||||||
|
materialAlternateStandardShader.Add(materialName);
|
||||||
|
}
|
||||||
|
materialData.roughness = 1.0f - materialData.roughness;
|
||||||
|
}
|
||||||
|
|
||||||
|
// remap the material name from the Unity material name to the fbx material name that it overrides
|
||||||
|
if (materialMappings.ContainsKey(materialName)) {
|
||||||
|
materialName = materialMappings[materialName];
|
||||||
|
}
|
||||||
|
if (!materialDatas.ContainsKey(materialName)) {
|
||||||
|
materialDatas.Add(materialName, materialData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static string GetMaterialTexture(Material material, string textureProperty) {
|
||||||
|
// ensure the texture property name exists in this material and return its texture directory path if so
|
||||||
|
string[] textureNames = material.GetTexturePropertyNames();
|
||||||
|
if (Array.IndexOf(textureNames, textureProperty) >= 0) {
|
||||||
|
Texture texture = material.GetTexture(textureProperty);
|
||||||
|
if (texture) {
|
||||||
|
foreach (var textureDependency in textureDependencies) {
|
||||||
|
string textureFile = textureDependency.Key;
|
||||||
|
if (Path.GetFileNameWithoutExtension(textureFile) == texture.name) {
|
||||||
|
return TEXTURES_DIRECTORY + "/" + textureFile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetMaterialMappings() {
|
||||||
|
materialMappings.Clear();
|
||||||
|
|
||||||
|
// store the mappings from fbx material name to the Unity material name overriding it using external fbx mapping
|
||||||
|
var objectMap = modelImporter.GetExternalObjectMap();
|
||||||
|
foreach (var mapping in objectMap) {
|
||||||
|
var material = mapping.Value as UnityEngine.Material;
|
||||||
|
if (material != null) {
|
||||||
|
materialMappings.Add(material.name, mapping.Key.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExportProjectWindow : EditorWindow {
|
class ExportProjectWindow : EditorWindow {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
High Fidelity, Inc.
|
High Fidelity, Inc.
|
||||||
Avatar Exporter
|
Avatar Exporter
|
||||||
Version 0.3
|
Version 0.3.1
|
||||||
|
|
||||||
|
|
||||||
Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter.
|
Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter.
|
||||||
|
|
||||||
|
|
Binary file not shown.
Loading…
Reference in a new issue