using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace Luneraria.EditorTools
{
///
/// Import rules for every FBX of the Blender pipeline under Assets/Luneraria/Art (U2A).
/// Metres stay metres, Blender's Z-up axes are converted by baking (no root rotation),
/// normals come from the file, tangents are computed with MikkTSpace (as in Blender), rigs are
/// generic with the Blender bone names, and clips get their Blender action names, loop flags
/// and contact events.
///
public sealed class LuneModelPostprocessor : AssetPostprocessor
{
public const string ArtRoot = "Assets/Luneraria/Art/";
/// Raised on the figure at the hit frame of attack and ability clips.
public const string ContactEvent = "OnLuneContact";
public override uint GetVersion() { return 5; }
void OnPreprocessModel()
{
if (!assetPath.StartsWith(ArtRoot) || !assetPath.EndsWith(".fbx"))
return;
ExportReport report = ExportReport.ForModel(assetPath);
var importer = (ModelImporter)assetImporter;
importer.globalScale = 1f;
importer.useFileScale = true;
importer.bakeAxisConversion = true;
importer.importCameras = false;
importer.importLights = false;
importer.importVisibility = false;
importer.importBlendShapes = false;
importer.importNormals = ModelImporterNormals.Import;
importer.importTangents = ModelImporterTangents.CalculateMikk;
importer.meshCompression = ModelImporterMeshCompression.Off;
importer.isReadable = true;
importer.materialImportMode = ModelImporterMaterialImportMode.ImportViaMaterialDescription;
importer.materialLocation = ModelImporterMaterialLocation.InPrefab;
bool skinned = report != null && report.Skinned;
importer.animationType = skinned ? ModelImporterAnimationType.Generic : ModelImporterAnimationType.None;
importer.avatarSetup = skinned ? ModelImporterAvatarSetup.CreateFromThisModel : ModelImporterAvatarSetup.NoAvatar;
importer.importAnimation = skinned;
importer.optimizeGameObjects = false;
importer.animationCompression = ModelImporterAnimationCompression.Off;
importer.resampleCurves = false;
}
///
/// U5.5: a figure with a lane copy (meshes <body>_LOD0/_LOD1, Unity builds the LODGroup
/// from the names) switches to it below the screen height the generator names; the lane
/// copy is never culled, so a figure never vanishes at map zoom.
///
void OnPostprocessModel(GameObject root)
{
if (!assetPath.StartsWith(ArtRoot) || !assetPath.EndsWith(".fbx"))
return;
LODGroup group = root.GetComponentInChildren();
ExportReport report = ExportReport.ForModel(assetPath);
var lod = report?.Data["generator"]?["lod"];
if (group == null || lod == null)
return;
LOD[] lods = group.GetLODs();
if (lods.Length != 2)
return;
lods[0].screenRelativeTransitionHeight = (float)lod["lod0_screen"];
lods[1].screenRelativeTransitionHeight = 0f;
group.SetLODs(lods);
}
void OnPreprocessAnimation()
{
if (!assetPath.StartsWith(ArtRoot) || !assetPath.EndsWith(".fbx"))
return;
ExportReport report = ExportReport.ForModel(assetPath);
if (report == null || !report.Skinned)
return;
var importer = (ModelImporter)assetImporter;
var blender = report.Clips.ToDictionary(c => (string)c["name"], c => c);
var clips = new List();
foreach (ModelImporterClipAnimation clip in importer.defaultClipAnimations)
{
// Blender names takes "|"; Unity keeps the action name.
string name = clip.takeName.Contains("|") ? clip.takeName.Substring(clip.takeName.LastIndexOf('|') + 1) : clip.takeName;
clip.name = name;
if (blender.TryGetValue(name, out var facts))
{
clip.loopTime = (bool)facts["loop"];
double contact = (double)facts["contact_seconds"];
double seconds = (double)facts["seconds"];
if (contact > 0.0 && seconds > 0.0)
clip.events = new[] { new AnimationEvent { functionName = ContactEvent, time = (float)(contact / seconds) } };
}
clips.Add(clip);
}
importer.clipAnimations = clips.ToArray();
}
}
}