Asset Dump

This commit is contained in:
2026-06-03 13:46:13 -07:00
parent e362aaeb43
commit 9091388bc2
20821 changed files with 26544125 additions and 58 deletions
@@ -0,0 +1,234 @@
// Editor-only art-pipeline helpers for Project-M.
// Converts the HDRP-authored BefourStudios props to stock URP/Lit materials
// (Entities-Graphics compatible) and remaps placed prop instances onto them.
// Reusable for future Synty/asset packs: edit CuratedPrefabNames or call the
// public static methods. Lives in Assembly-CSharp-Editor (Editor folder).
//
// Re-run contract: ConvertCurated() OVERWRITES existing M_Env_* materials in place
// (GUID preserved so scene/prefab refs stay valid) -> any hand-tuning of a converted
// material is reset to the generated values on the next run.
#if UNITY_EDITOR
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;
namespace ProjectM.EditorTools
{
public static class EnvArtTools
{
const string EnvDir = "Assets/_Project/Materials/Env";
const string ArtPrefabs = "Assets/BefourStudios/Art/Prefabs/";
const string FallbackPath = EnvDir + "/M_Env_Fallback.mat";
// Candidate property names across HDRP/Lit and HDRP ShaderGraph variants.
static readonly string[] BaseProps = { "_BaseColorMap", "_BaseTexture", "_BaseMap", "_MainTex", "_AlbedoTexture" };
static readonly string[] NormalProps = { "_NormalMap", "_NormalTexture", "_BumpMap" };
// REAL color tints only. NOT _BaseColorMultiply (a float scalar on these sources) —
// reading a float via GetColor() returns (0,0,0) and would black out the albedo.
static readonly string[] TintProps = { "_BaseColor", "_AlbedoTint", "_Color" };
static readonly string[] EmissiveProps = { "_EmissiveColor", "_EmissionColor" };
// Source prefabs whose materials get converted. Covers all placed home-base props
// plus the two ghost props (SM_Storage, SM_Battery).
static readonly string[] CuratedPrefabNames =
{
"SM_ModularBlockCentral", "SM_ModularBlockEdge01", "SM_ModularBlockEdge02",
"SM_ModularBlockCorner01", "SM_ModularBlockCorner02",
"SM_Dome01", "SM_DomeDoor",
"SM_Battery", "SM_BatteryCharger", "SM_BatteryPod",
"SM_Crate01", "SM_Crate02", "SM_Crate03",
"SM_Plant07", "SM_Plant08", "SM_Plant16",
"SM_LightPole", "SM_SolarPanelModule", "SM_Storage",
};
[MenuItem("ProjectM/Art/1. Convert Curated Env Materials")]
public static int ConvertCurated()
{
EnsureDir();
EnsureFallback();
// Dedup by DESTINATION path; warn on collisions (same-named sources from different folders).
var byDest = new Dictionary<string, Material>();
foreach (var name in CuratedPrefabNames)
{
var go = AssetDatabase.LoadAssetAtPath<GameObject>(ArtPrefabs + name + ".prefab");
if (go == null) { Debug.LogWarning("[EnvArt] missing prefab: " + name); continue; }
foreach (var r in go.GetComponentsInChildren<Renderer>(true))
foreach (var m in r.sharedMaterials)
{
if (m == null || string.IsNullOrEmpty(m.name) || m.name == "No Name") continue;
var dest = EnvPathFor(m.name);
if (byDest.TryGetValue(dest, out var prev))
{
if (prev != m)
Debug.LogWarning($"[EnvArt] name collision -> {dest}: keeping '{AssetDatabase.GetAssetPath(prev)}', ignoring '{AssetDatabase.GetAssetPath(m)}'");
continue;
}
byDest[dest] = m;
}
}
int n = 0;
foreach (var kv in byDest) { ConvertToUrp(kv.Value, kv.Key); n++; }
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log($"[EnvArt] Converted {n} materials into {EnvDir} (overwrites regenerate; hand edits are lost).");
return n;
}
public static string EnvPathFor(string srcMaterialName)
{
string s = srcMaterialName;
if (s.StartsWith("M_")) s = s.Substring(2);
else if (s.StartsWith("MI_")) s = s.Substring(3);
s = s.Replace(" ", "");
return $"{EnvDir}/M_Env_{s}.mat";
}
// Build (or overwrite, preserving GUID) a stock URP/Lit material from an HDRP source.
public static Material ConvertToUrp(Material src, string destPath)
{
var sh = Shader.Find("Universal Render Pipeline/Lit");
var tmp = new Material(sh);
var baseTex = FindTex(src, BaseProps) as Texture2D;
var normTex = FindTex(src, NormalProps) as Texture2D;
if (baseTex != null) tmp.SetTexture("_BaseMap", baseTex);
Color tint = Color.white;
foreach (var c in TintProps)
if (HasColor(src, c)) { tint = src.GetColor(c); break; }
tint.a = 1f;
tmp.SetColor("_BaseColor", tint);
if (normTex != null) { tmp.SetTexture("_BumpMap", normTex); tmp.EnableKeyword("_NORMALMAP"); tmp.SetFloat("_BumpScale", 1f); }
// ORM channels don't match URP's _MetallicGlossMap, and there's no reflection probe here,
// so keep metallic LOW (high metallic + dark skybox reads near-black) and use uniform smoothness.
string nm = src.name.ToLowerInvariant();
float metallic = 0.10f, smooth = 0.45f;
if (nm.Contains("metal") || nm.Contains("modular") || nm.Contains("battery") || nm.Contains("solar") ||
nm.Contains("dome") || nm.Contains("container") || nm.Contains("charger")) { metallic = 0.20f; smooth = 0.50f; }
if (nm.Contains("glass") || nm.Contains("mirror")) { metallic = 0.0f; smooth = 0.85f; }
if (nm.Contains("plant") || nm.Contains("dirt") || nm.Contains("rock") ||
nm.Contains("rubber") || nm.Contains("plastic")) { metallic = 0.0f; smooth = 0.30f; }
tmp.SetFloat("_Metallic", metallic);
tmp.SetFloat("_Smoothness", smooth);
tmp.SetFloat("_WorkflowMode", 1f); // Metallic
bool clip = nm.Contains("plant")
|| (HasFloat(src, "_AlphaCutoffEnable") && src.GetFloat("_AlphaCutoffEnable") > 0.5f)
|| (HasFloat(src, "_AlphaClip") && src.GetFloat("_AlphaClip") > 0.5f);
if (clip)
{
tmp.SetFloat("_AlphaClip", 1f);
tmp.EnableKeyword("_ALPHATEST_ON");
tmp.SetFloat("_Cutoff", 0.4f);
tmp.renderQueue = 2450;
if (nm.Contains("plant")) tmp.SetFloat("_Cull", 0f); // double-sided foliage
}
// Emission only when the SOURCE _Emissive flag is on AND the name marks it a light fixture,
// so a default non-zero _EmissiveColor on the shader graph doesn't make everything glow
// (flat color emission can't reproduce the source emission mask anyway).
bool srcEmOn = HasFloat(src, "_Emissive") && src.GetFloat("_Emissive") > 0.5f;
bool nameEmissive = nm.Contains("emissive") || nm.Contains("hologram") || nm.Contains("screen") ||
nm.Contains("lightpole") || nm.Contains("lights");
if (srcEmOn && nameEmissive)
{
Color ec = Color.white;
foreach (var c in EmissiveProps)
if (HasColor(src, c)) { ec = src.GetColor(c); break; }
float emInt = HasFloat(src, "_EmissiveIntensity") ? Mathf.Clamp(src.GetFloat("_EmissiveIntensity"), 1f, 4f) : 1.5f;
var e = new Color(ec.r, ec.g, ec.b) * emInt;
if (e.maxColorComponent > 0.05f)
{
tmp.EnableKeyword("_EMISSION");
tmp.SetColor("_EmissionColor", e);
tmp.globalIlluminationFlags = MaterialGlobalIlluminationFlags.BakedEmissive;
}
}
var existing = AssetDatabase.LoadAssetAtPath<Material>(destPath);
if (existing != null)
{
EditorUtility.CopySerialized(tmp, existing);
Object.DestroyImmediate(tmp);
EditorUtility.SetDirty(existing);
return existing;
}
AssetDatabase.CreateAsset(tmp, destPath);
return tmp;
}
// Reassign every renderer material on a placed prop instance to its converted Env equivalent.
public static int RemapRenderersToEnv(GameObject root)
{
EnsureFallback();
var fallback = AssetDatabase.LoadAssetAtPath<Material>(FallbackPath);
int swapped = 0, fell = 0;
foreach (var r in root.GetComponentsInChildren<Renderer>(true))
{
var src = r.sharedMaterials;
var dst = new Material[src.Length];
for (int i = 0; i < src.Length; i++)
{
if (src[i] == null) { dst[i] = fallback; fell++; continue; }
var env = AssetDatabase.LoadAssetAtPath<Material>(EnvPathFor(src[i].name));
if (env != null) { dst[i] = env; swapped++; } else { dst[i] = fallback; fell++; }
}
r.sharedMaterials = dst;
}
if (fell > 0) Debug.Log($"[EnvArt] RemapRenderersToEnv {root.name}: {swapped} mapped, {fell} -> fallback");
return swapped;
}
static bool HasColor(Material m, string prop)
{
if (!m.HasProperty(prop)) return false;
int idx = m.shader.FindPropertyIndex(prop);
if (idx < 0) return false;
var t = m.shader.GetPropertyType(idx);
return t == ShaderPropertyType.Color || t == ShaderPropertyType.Vector;
}
static bool HasFloat(Material m, string prop)
{
if (!m.HasProperty(prop)) return false;
int idx = m.shader.FindPropertyIndex(prop);
if (idx < 0) return false;
var t = m.shader.GetPropertyType(idx);
return t == ShaderPropertyType.Float || t == ShaderPropertyType.Range;
}
static Texture FindTex(Material m, string[] names)
{
foreach (var n in names)
{
if (!m.HasProperty(n)) continue;
int idx = m.shader.FindPropertyIndex(n);
if (idx < 0 || m.shader.GetPropertyType(idx) != ShaderPropertyType.Texture) continue;
var t = m.GetTexture(n);
if (t != null) return t;
}
return null;
}
static void EnsureDir()
{
if (!AssetDatabase.IsValidFolder(EnvDir))
AssetDatabase.CreateFolder("Assets/_Project/Materials", "Env");
}
static void EnsureFallback()
{
if (AssetDatabase.LoadAssetAtPath<Material>(FallbackPath) != null) return;
EnsureDir();
var m = new Material(Shader.Find("Universal Render Pipeline/Lit"));
m.SetColor("_BaseColor", new Color(0.55f, 0.57f, 0.60f));
m.SetFloat("_Metallic", 0.1f);
m.SetFloat("_Smoothness", 0.45f);
AssetDatabase.CreateAsset(m, FallbackPath);
}
}
}
#endif