Second commit

This commit is contained in:
ThomasCharester committed 2026-09-20 15:26:03 +03:00
commit 36840f3ad4
999 files changed
+61364

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.IO;
using System.Threading.Tasks;
namespace TinyGoose.Tremble
{
public static class DirectoryUtil
{
private static readonly char[] s_PathSeparators = { '\\', '/' };
public static void EmptyAndCreateDirectory(string folder)
{
if (Directory.Exists(folder))
{
Directory.Delete(folder, true);
}
CreateAllDirectories(folder);
}
public static int CreateAllDirectories(string folders)
{
int numCreated = 0;
string[] pathFolders = folders.Split(s_PathSeparators);
if (pathFolders.Length <= 1)
return numCreated;
string path = pathFolders[0];
for (int i = 1; i < pathFolders.Length; i++)
{
// Path.Combine breaks when it's absolute (because /Users becomes Users)
path += Path.DirectorySeparatorChar + pathFolders[i];
if (i == pathFolders.Length - 1 && pathFolders[i].Contains('.'))
{
// Last component and it has a dot - it's a file!
break;
}
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
numCreated++;
}
}
return numCreated;
}
public static async Task<bool> CopyDirectory(string source, string destination)
{
DirectoryInfo dir = new(source);
// Check if the source directory exists
if (!dir.Exists)
return false;
EmptyAndCreateDirectory(destination);
await Task.Run(async () =>
{
foreach (FileInfo file in dir.GetFiles())
{
string targetFilePath = Path.Combine(destination, file.Name);
file.CopyTo(targetFilePath);
}
// Recurse!
foreach (DirectoryInfo subDir in dir.GetDirectories())
{
string newDestinationDir = Path.Combine(destination, subDir.Name);
await CopyDirectory(subDir.FullName, newDestinationDir);
}
});
return true;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 9657858345914445b2cabe032ca32b33
timeCreated: 1707729759
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/DirectoryUtil.cs
uploadId: 939664
+327
View File
@@ -0,0 +1,327 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using Type = System.Type;
namespace TinyGoose.Tremble
{
public static class FieldUtil
{
private static readonly string[] KNOWN_PREFIXES = { "m_", "_" };
private static readonly Dictionary<Type, object[]> s_CachedTypeAttributes = new();
private static readonly Dictionary<MemberInfo, object[]> s_CachedFieldAttributes = new();
private static readonly Dictionary<Type, FieldInfo[]> s_AllInstanceFieldsIncludingPrivateAndBaseClasses = new();
public static bool TryGetCustomAttribute<TAttribute>(this Type type, out TAttribute att)
where TAttribute : Attribute
{
att = type.GetCachedAttribute<TAttribute>();
return att != null;
}
public static bool HasCustomAttribute<TAttribute>(this FieldInfo field)
where TAttribute : Attribute
{
return field.GetCachedAttribute<TAttribute>() != null;
}
public static bool HasCustomAttribute<TAttribute>(this Type type)
where TAttribute : Attribute
{
return type.GetCachedAttribute<TAttribute>() != null;
}
public static void GetCustomAttributes<TAttribute1>(this MemberInfo member, out TAttribute1 attr1)
where TAttribute1 : Attribute
{
attr1 = default;
foreach (Attribute attribute in member.GetCachedAttributes())
{
if (attribute is TAttribute1 tAttribute1)
attr1 = tAttribute1;
}
}
public static void GetCustomAttributes<TAttribute1, TAttribute2>(this MemberInfo member, out TAttribute1 attr1, out TAttribute2 attr2)
where TAttribute1 : Attribute
where TAttribute2 : Attribute
{
attr1 = default;
attr2 = default;
foreach (Attribute attribute in member.GetCachedAttributes())
{
if (attribute is TAttribute1 tAttribute1)
attr1 = tAttribute1;
if (attribute is TAttribute2 tAttribute2)
attr2 = tAttribute2;
}
}
public static void GetCustomAttributes<TAttribute1, TAttribute2, TAttribute3>(this MemberInfo member, out TAttribute1 attr1, out TAttribute2 attr2, out TAttribute3 attr3)
where TAttribute1 : Attribute
where TAttribute2 : Attribute
where TAttribute3 : Attribute
{
attr1 = default;
attr2 = default;
attr3 = default;
foreach (Attribute attribute in member.GetCachedAttributes())
{
if (attribute is TAttribute1 tAttribute1)
attr1 = tAttribute1;
if (attribute is TAttribute2 tAttribute2)
attr2 = tAttribute2;
if (attribute is TAttribute3 tAttribute3)
attr3 = tAttribute3;
}
}
public static void GetCustomAttributes<TAttribute1, TAttribute2, TAttribute3, TAttribute4>(this MemberInfo member, out TAttribute1 attr1, out TAttribute2 attr2, out TAttribute3 attr3, out TAttribute4 attr4)
where TAttribute1 : Attribute
where TAttribute2 : Attribute
where TAttribute3 : Attribute
where TAttribute4 : Attribute
{
attr1 = default;
attr2 = default;
attr3 = default;
attr4 = default;
foreach (Attribute attribute in member.GetCachedAttributes())
{
if (attribute is TAttribute1 tAttribute1)
attr1 = tAttribute1;
if (attribute is TAttribute2 tAttribute2)
attr2 = tAttribute2;
if (attribute is TAttribute3 tAttribute3)
attr3 = tAttribute3;
if (attribute is TAttribute4 tAttribute4)
attr4 = tAttribute4;
}
}
public static void GetCustomAttributes<TAttribute1, TAttribute2, TAttribute3, TAttribute4, TAttribute5>(this MemberInfo member, out TAttribute1 attr1, out TAttribute2 attr2, out TAttribute3 attr3, out TAttribute4 attr4, out TAttribute5 attr5)
where TAttribute1 : Attribute
where TAttribute2 : Attribute
where TAttribute3 : Attribute
where TAttribute4 : Attribute
where TAttribute5 : Attribute
{
attr1 = default;
attr2 = default;
attr3 = default;
attr4 = default;
attr5 = default;
foreach (Attribute attribute in member.GetCachedAttributes())
{
if (attribute is TAttribute1 tAttribute1)
attr1 = tAttribute1;
if (attribute is TAttribute2 tAttribute2)
attr2 = tAttribute2;
if (attribute is TAttribute3 tAttribute3)
attr3 = tAttribute3;
if (attribute is TAttribute4 tAttribute4)
attr4 = tAttribute4;
if (attribute is TAttribute5 tAttribute5)
attr5 = tAttribute5;
}
}
public static void GetCustomAttributes<TAttribute1, TAttribute2, TAttribute3, TAttribute4, TAttribute5, TAttribute6>(this MemberInfo member, out TAttribute1 attr1, out TAttribute2 attr2, out TAttribute3 attr3, out TAttribute4 attr4, out TAttribute5 attr5, out TAttribute6 attr6)
where TAttribute1 : Attribute
where TAttribute2 : Attribute
where TAttribute3 : Attribute
where TAttribute4 : Attribute
where TAttribute5 : Attribute
where TAttribute6 : Attribute
{
attr1 = default;
attr2 = default;
attr3 = default;
attr4 = default;
attr5 = default;
attr6 = default;
foreach (Attribute attribute in member.GetCachedAttributes())
{
if (attribute is TAttribute1 tAttribute1)
attr1 = tAttribute1;
if (attribute is TAttribute2 tAttribute2)
attr2 = tAttribute2;
if (attribute is TAttribute3 tAttribute3)
attr3 = tAttribute3;
if (attribute is TAttribute4 tAttribute4)
attr4 = tAttribute4;
if (attribute is TAttribute5 tAttribute5)
attr5 = tAttribute5;
if (attribute is TAttribute6 tAttribute6)
attr6 = tAttribute6;
}
}
private static object[] GetCachedAttributes(this MemberInfo member)
{
if (s_CachedFieldAttributes.TryGetValue(member, out object[] attributes))
return attributes;
attributes = member.GetCustomAttributes(true);
s_CachedFieldAttributes[member] = attributes;
return attributes;
}
private static object[] GetCachedAttributes(this Type type)
{
if (s_CachedTypeAttributes.TryGetValue(type, out object[] attributes))
return attributes;
attributes = type.GetCustomAttributes(true);
s_CachedTypeAttributes[type] = attributes;
return attributes;
}
private static TAttribute GetCachedAttribute<TAttribute>(this MemberInfo member)
{
foreach (object availableAttribute in member.GetCachedAttributes())
{
if (availableAttribute is TAttribute castedAttribute)
return castedAttribute;
}
return default;
}
private static TAttribute GetCachedAttribute<TAttribute>(this Type type)
{
foreach (object availableAttribute in type.GetCachedAttributes())
{
if (availableAttribute is TAttribute castedAttribute)
return castedAttribute;
}
return default;
}
public static string GetFieldNameInMap(this string fieldName, NamingConvention fnc)
{
// Remove prefixes
if (fnc != NamingConvention.PreserveExact)
{
foreach (string prefix in KNOWN_PREFIXES)
{
if (!fieldName.StartsWithInvariant(prefix, caseSensitive: true))
continue;
fieldName = fieldName.Substring(prefix.Length);
}
}
return fieldName.ToNamingConvention(fnc);
}
public static int GetBitForSpawnFlagsField(this SpawnFlagsAttribute spawnFlags, TrembleSyncSettings syncSettings, MemberInfo mi)
{
if (spawnFlags is { Bit: >= 0 })
return spawnFlags.Bit;
int bit = -1;
foreach (FieldInfo availableField in mi.DeclaringType.GetAllInstanceFieldsIncludingPrivateAndBaseClasses())
{
// This isn't a boolean!
if (availableField.FieldType != typeof(Boolean))
continue;
availableField.GetCustomAttributes(
out SpawnFlagsAttribute sfa,
out NoSpawnFlagsAttribute noSfa,
out TrembleAttribute ta,
out NoTrembleAttribute nta,
out SerializeField sf
);
// This is categorically NOT a spawnflags var
if (noSfa != null)
continue;
// Not for Tremble
if (nta != null)
continue;
// This field doesn't have spawn flags - doesn't count!
if (sfa == null && !syncSettings.AlwaysPackBoolsIntoSpawnFlags)
continue;
// No tremble attribute, or no SerializeField/we're not considering SFs
if (ta == null && (sf == null || !syncSettings.SyncSerializedFields))
continue;
// It's a spawnflags field, increment
bit++;
// It's the one we're looking for - stop!
if (availableField == mi)
break;
}
return bit;
}
public static Type GetFieldOrPropertyType(this MemberInfo mi)
{
switch (mi)
{
case PropertyInfo pi:
return pi.PropertyType;
case FieldInfo fi:
return fi.FieldType;
}
Debug.LogError($"Unsupported field type {mi.GetType()}!");
return null;
}
public static Type GetFieldOrPropertyTypeOrElementType(this MemberInfo mi)
{
Type type = mi.GetFieldOrPropertyType();
if (type is { IsArray: true })
{
type = type.GetElementType();
}
return type;
}
public static FieldInfo[] GetAllInstanceFieldsIncludingPrivateAndBaseClasses(this Type t)
{
Type originalType = t;
if (!s_AllInstanceFieldsIncludingPrivateAndBaseClasses.TryGetValue(originalType, out FieldInfo[] fields))
{
List<FieldInfo> fieldList = new(64);
while (t != null && (t.Namespace == null || !t.Namespace.ContainsInvariant("UnityEngine", caseSensitive: true)))
{
fieldList.InsertRange(0, t.GetFields(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.NonPublic | BindingFlags.Public));
t = t.BaseType;
}
fields = fieldList.ToArray();
s_AllInstanceFieldsIncludingPrivateAndBaseClasses[originalType] = fields;
}
return fields;
}
}
}
+10
View File
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 52da94c615dc49adbb44d9880ad075c6
timeCreated: 1705686190
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/FieldUtil.cs
uploadId: 939664
+39
View File
@@ -0,0 +1,39 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Globalization;
using UnityEngine;
namespace TinyGoose.Tremble
{
public static class FloatFormatUtil
{
private static readonly char[] s_FormatArr = { 'F', '0' };
public static string ToStringInvariant(this float f, int decimals = 2)
{
if (decimals > 9)
throw new ArgumentException("ToStringInvariant only supports up to 9 decimal places :(");
s_FormatArr[1] = (char)('0' + decimals);
return f.ToString(new(s_FormatArr), CultureInfo.InvariantCulture);
}
public static string ToStringInvariant(this Vector2 v, int decimals = 2)
=> $"{v.x.ToStringInvariant(decimals)} {v.y.ToStringInvariant(decimals)}";
public static string ToStringInvariant(this Vector3 v, int decimals = 2)
=> $"{v.x.ToStringInvariant(decimals)} {v.y.ToStringInvariant(decimals)} {v.z.ToStringInvariant(decimals)}";
public static string ToStringInvariant(this Color c, int decimals = 2)
=> $"{c.r.ToStringInvariant(decimals)} {c.g.ToStringInvariant(decimals)} {c.b.ToStringInvariant(decimals)}";
public static bool TryParseFloat(this string s, out float f)
=> float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out f);
public static bool TryParseFloat(this ReadOnlySpan<char> s, out float f)
=> float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out f);
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 42bb3c4e19fb4804bfaa5dd98d17cee0
timeCreated: 1722209446
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/FloatFormatUtil.cs
uploadId: 939664
+28
View File
@@ -0,0 +1,28 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using UnityEngine;
namespace TinyGoose.Tremble
{
public static class GameObjectUtil
{
public static GameObject Find_Recursive(this GameObject go, string findName)
{
if (go.name.EqualsInvariant(findName, caseSensitive: true))
return go;
for (int i = 0; i < go.transform.childCount; i++)
{
GameObject inner = Find_Recursive(go.transform.GetChild(i).gameObject, findName);
if (inner)
return inner;
}
return null;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 9a7a9d2123c941af9e5751aad84bf341
timeCreated: 1724939527
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/GameObjectUtil.cs
uploadId: 939664
+11
View File
@@ -0,0 +1,11 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
// This allows you to use properties with init-only setters,
// e.g. public int MyProperty { get; init; }
namespace System.Runtime.CompilerServices
{
public struct IsExternalInit {}
}
+10
View File
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: b9c41efc8d38494ebd53a358fbff6c96
timeCreated: 1707729573
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/InitHack.cs
uploadId: 939664
+361
View File
@@ -0,0 +1,361 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
// This class based on BSP Map Tools for Unity by John Evans (evans3d512@gmail.com)
//
using System;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
namespace TinyGoose.Tremble
{
public static class MD3Util
{
private const int MAX_SURF_INDICES = 12000;
private const string IDP3_SIGNATURE = "IDP3";
private const int IDP3_VERSION = 15;
public static bool HasModel(GameObject prefab, out Bounds bounds)
{
bool result = false;
bounds = new();
foreach (MeshFilter mf in prefab.GetComponentsInChildren<MeshFilter>())
{
MeshRenderer mr = mf.GetComponent<MeshRenderer>();
if (!mr)
continue;
if (!mf.sharedMesh)
continue;
result = true;
GetTransformedMeshVertices(mr.transform, isSkinnedMesh: false, mf.sharedMesh, ref bounds);
}
foreach (SkinnedMeshRenderer smr in prefab.GetComponentsInChildren<SkinnedMeshRenderer>())
{
if (!smr.sharedMesh)
continue;
result = true;
GetTransformedMeshVertices(smr.transform, isSkinnedMesh: true, smr.sharedMesh, ref bounds);
}
return result;
}
private static Bounds GetPrefabBounds(GameObject prefab)
{
Bounds boundingBox = new();
foreach (MeshFilter mf in prefab.GetComponentsInChildren<MeshFilter>())
{
MeshRenderer mr = mf.GetComponent<MeshRenderer>();
if (!mr)
continue;
if (!mf.sharedMesh)
continue;
GetTransformedMeshVertices(mr.transform, isSkinnedMesh: false, mf.sharedMesh, ref boundingBox);
}
foreach (SkinnedMeshRenderer smr in prefab.GetComponentsInChildren<SkinnedMeshRenderer>())
{
if (!smr.sharedMesh)
continue;
GetTransformedMeshVertices(smr.transform, isSkinnedMesh: true, smr.sharedMesh, ref boundingBox);
}
return boundingBox;
}
public static Vector3 GetPrefabUnitySpawnOffset(GameObject prefab, float importScale)
{
Bounds boundingBox = GetPrefabBounds(prefab);
Vector3 boundsMin = boundingBox.min.UnityToQ3Vector(scale: importScale);
Vector3 boundsMax = boundingBox.max.UnityToQ3Vector(scale: importScale);
Vector3 boundsCentre = (boundsMin + boundsMax) / 2f;
return -boundsCentre.Q3ToUnityVector(importScale);
}
public static bool SaveMD3(GameObject prefab, string md3Path, MaterialNameLookup materialNameLookup, float importScale, out Bounds boundingBox)
{
// Gather MD3 surfaces to write from renderers
List<MD3Surface> surfaces = GatherMD3SurfacesFromRenderers(prefab, materialNameLookup, out boundingBox, out int allSurfacesSize);
if (surfaces.Count == 0)
return false;
// Now write the MD3 file
int fileSize = allSurfacesSize + 164;
string modelName = GetPathInFolder(md3Path, "baseq3");
using FileStream stream = new(md3Path, FileMode.Create, FileAccess.Write, FileShare.None, 32768);
using StreamWriteIO writer = new(stream);
// IDP3 signature & version
writer.WriteString(IDP3_SIGNATURE);
writer.WriteInt32(IDP3_VERSION);
// Model name (64 chars)
writer.WriteString(modelName, 64);
// Flags, frames, tags, surfaces, skins
writer.WriteInt32(0); // flags 0
writer.WriteInt32(1); // frames
writer.WriteInt32(0); // tags 0
writer.WriteInt32(surfaces.Count); // surfaces
writer.WriteInt32(0); // skins 0
// Write metrics
const int FRAME_OFFSET = 108;
writer.WriteInt32(FRAME_OFFSET);
const int TAGS_OFFSET = 164;
writer.WriteInt32(TAGS_OFFSET);
const int SURFACE_OFFSET = 164;
writer.WriteInt32(SURFACE_OFFSET);
writer.WriteInt32(fileSize); // file size bytes
// Write first frame
Vector3 boundsMin = boundingBox.min.UnityToQ3Vector(scale: importScale);
Vector3 boundsMax = boundingBox.max.UnityToQ3Vector(scale: importScale);
Vector3 boundsCentre = (boundsMin + boundsMax) / 2f;
FixBounds(ref boundsMin, ref boundsMax);
writer.WriteFloat(boundsMin.x); // bounds (min)
writer.WriteFloat(boundsMin.y);
writer.WriteFloat(boundsMin.z);
writer.WriteFloat(boundsMax.x); // bounds (max)
writer.WriteFloat(boundsMax.y);
writer.WriteFloat(boundsMax.z);
writer.WriteFloat(0f); // origin
writer.WriteFloat(0f);
writer.WriteFloat(0f);
writer.WriteFloat((boundsMax - boundsMin).magnitude); // radius
writer.WriteString("frame1", 16); // frame name
// Write surfaces
for (int surfIdx = 0; surfIdx < surfaces.Count; surfIdx++)
{
MD3Surface surface = surfaces[surfIdx];
int surfVertCount = surface.Vertices.Length;
int triangleCount = surfVertCount / 3;
int triangleOffset = 176;
int shaderOffset = 108;
int uvsOffset = triangleOffset + triangleCount * 12;
int vtxOffset = uvsOffset + surfVertCount * 8;
writer.WriteString(IDP3_SIGNATURE); // IDP3
// Surface
writer.WriteString(surface.Name, 64); // surf name
writer.WriteInt32(0); // flags 0
writer.WriteInt32(1); // frames
writer.WriteInt32(1); // shaders
writer.WriteInt32(surfVertCount);
writer.WriteInt32(triangleCount);
writer.WriteInt32(triangleOffset);
writer.WriteInt32(shaderOffset);
writer.WriteInt32(uvsOffset);
writer.WriteInt32(vtxOffset);
writer.WriteInt32(surface.DataSize);
// Shader
writer.WriteString($"models/{surface.Texture}.png", 64); // shader name
writer.WriteInt32(surfIdx); // Shader index
// Tris
for (int triIdx = 0; triIdx < surfVertCount; triIdx++)
{
writer.WriteInt32(triIdx);
}
// Surfs
for (int uvIdx = 0; uvIdx < surface.Uvs.Length; uvIdx++)
{
writer.WriteFloat(surface.Uvs[uvIdx].x);
writer.WriteFloat(1f - surface.Uvs[uvIdx].y);
}
// Verts
for (int vertIdx = 0; vertIdx < surfVertCount; vertIdx++)
{
Vector3 md3Vector = surface.Vertices[vertIdx].UnityToQ3Vector(scale: importScale);
md3Vector -= boundsCentre;
writer.WriteInt16((short)(md3Vector.x * 64f));
writer.WriteInt16((short)(md3Vector.y * 64f));
writer.WriteInt16((short)(md3Vector.z * 64f));
writer.WriteNormalAsLatLong(surface.Normals[vertIdx]);
}
}
return true;
}
private static List<MD3Surface> GatherMD3SurfacesFromRenderers(GameObject prefab, MaterialNameLookup materialNameLookup, out Bounds boundingBox, out int allSurfacesSize)
{
List<MD3Surface> surfaces = new();
allSurfacesSize = 0;
boundingBox = new();
foreach (MeshFilter mf in prefab.GetComponentsInChildren<MeshFilter>())
{
MeshRenderer mr = mf.GetComponent<MeshRenderer>();
if (!mr)
continue;
AddMeshToMD3(mr.transform, isSkinnedMesh: false, mf.sharedMesh, mr.sharedMaterials, surfaces, materialNameLookup, ref allSurfacesSize, ref boundingBox);
}
foreach (SkinnedMeshRenderer smr in prefab.GetComponentsInChildren<SkinnedMeshRenderer>())
{
AddMeshToMD3(smr.transform, isSkinnedMesh: true, smr.sharedMesh, smr.sharedMaterials, surfaces, materialNameLookup, ref allSurfacesSize, ref boundingBox);
}
return surfaces;
}
private static void FixBounds(ref Vector3 min, ref Vector3 max)
{
Vector3 newMin = new(Mathf.Min(min.x, max.x), Mathf.Min(min.y, max.y), Mathf.Min(min.z, max.z));
Vector3 newMax = new(Mathf.Max(min.x, max.x), Mathf.Max(min.y, max.y), Mathf.Max(min.z, max.z));
min = newMin;
max = newMax;
}
private static Vector3[] GetTransformedMeshVertices(Transform meshTransform, bool isSkinnedMesh, Mesh unityMesh, ref Bounds allBounds)
{
// Gather mesh data
Vector3[] meshVertices = unityMesh.vertices;
Vector3 min = allBounds.min;
Vector3 max = allBounds.max;
Matrix4x4 ltwMatrix = meshTransform.localToWorldMatrix;
for (int i = 0; i < meshVertices.Length; i++)
{
// Transform vertex according to the prefab root it's from
if (isSkinnedMesh)
{
meshVertices[i] = meshTransform.TransformVector(meshVertices[i]);
}
else
{
meshVertices[i] = ltwMatrix.MultiplyPoint(meshVertices[i]);
}
// Clamp vertices, MD3 uses Int16 vectors for vertex positions, so each vertex can be in range from -12.75 to 12.75
float meshVertX = meshVertices[i].x; // = Mathf.Clamp(meshVertices[i].x, -12.75f, +12.75f);
float meshVertY = meshVertices[i].y; // = Mathf.Clamp(meshVertices[i].y, -12.75f, +12.75f);
float meshVertZ = meshVertices[i].z; // = Mathf.Clamp(meshVertices[i].z, -12.75f, +12.75f);
// Inlined Mathf.Min and Mathf.Max
min.x = min.x < meshVertX ? min.x : meshVertX;
min.y = min.y < meshVertY ? min.y : meshVertY;
min.z = min.z < meshVertZ ? min.z : meshVertZ;
max.x = max.x > meshVertX ? max.x : meshVertX;
max.y = max.y > meshVertY ? max.y : meshVertY;
max.z = max.z > meshVertZ ? max.z : meshVertZ;
}
allBounds.SetMinMax(min, max);
return meshVertices;
}
private static void AddMeshToMD3(Transform meshTransform, bool isSkinnedMesh, Mesh unityMesh, Material[] materials, List<MD3Surface> surfaces, MaterialNameLookup materialNameLookup, ref int allSurfacesSize, ref Bounds allBounds)
{
// Gather triangles per submesh
List<int[]> submeshTriIndices = new(unityMesh.subMeshCount);
for (int i = 0; i < unityMesh.subMeshCount; i++)
{
submeshTriIndices.Add(unityMesh.GetTriangles(i));
}
// Gather mesh data
Vector3[] meshVertices = GetTransformedMeshVertices(meshTransform, isSkinnedMesh, unityMesh, ref allBounds);
Vector3[] meshNormals = unityMesh.normals;
Vector2[] meshUVs = unityMesh.uv;
// Write one surface per Unity submesh
for (int submeshIdx = 0; submeshIdx < unityMesh.subMeshCount; submeshIdx++)
{
if (submeshTriIndices[submeshIdx].Length >= MAX_SURF_INDICES)
{
Debug.LogError("Mesh has too many surfaces - skipping!");
continue;
}
// Add verts and triangles
int vertIndexCount = submeshTriIndices[submeshIdx].Length;
Vector3[] surfaceVertices = new Vector3[vertIndexCount];
Vector3[] surfaceNormals = new Vector3[vertIndexCount];
Vector2[] surfaceUVs = new Vector2[vertIndexCount];
int[] thisSubmeshTriIndices = submeshTriIndices[submeshIdx];
for (int vIdx = 0; vIdx < vertIndexCount; vIdx++)
{
int index = thisSubmeshTriIndices[vIdx];
surfaceVertices[vIdx] = meshVertices[index];
surfaceNormals[vIdx] = meshNormals[index];
// Some meshes have no UVs (uh-oh...)
if (meshUVs.Length > 0)
{
surfaceUVs[vIdx] = meshUVs[index];
}
}
// Write material names
string textureName;
if (materials[submeshIdx] && materialNameLookup != null)
{
string path = TrembleAssetLoader.GetPath(materials[submeshIdx]);
textureName = materialNameLookup.GetPrefabNameFromMaterialPath(path);
}
else
{
textureName = "missing";
}
// Create surface
MD3Surface currentSurface = new()
{
Name = $"surface{surfaces.Count}",
Texture = textureName,
Vertices = surfaceVertices,
Normals = surfaceNormals,
Uvs = surfaceUVs,
};
surfaces.Add(currentSurface);
allSurfacesSize += currentSurface.DataSize;
}
}
private static string GetPathInFolder(string path, string folder)
{
int i = path.IndexOfInvariant(folder + "/");
return path.Substring(i + folder.Length + 1);
}
}
}
+19
View File
@@ -0,0 +1,19 @@
fileFormatVersion: 2
guid: b6df817cee22fba4bb61fab7a3e48367
timeCreated: 1628505675
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/MD3Util.cs
uploadId: 939664
@@ -0,0 +1,468 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
using UnityEngine.Rendering;
using Debug = UnityEngine.Debug;
#if HDRP_INSTALLED
using File = System.IO.File;
#endif
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace TinyGoose.Tremble
{
/// <summary>
/// Camera/lighting setup for taking material screenshots. Do not use directly.
/// </summary>
public class MapTextureSnapshotter
{
// -----------------------------------------------------------------------------------------------------------------------------
// State
// -----------------------------------------------------------------------------------------------------------------------------
private GameObject m_SceneRoot;
private Camera m_Camera;
private MeshRenderer m_Renderer;
private readonly Dictionary<Vector2Int, TexturePair> m_RenderTextures = new();
private Texture2D m_CaptureTexture;
private Light m_StudioLight;
private AmbientMode m_OriginalAmbientMode;
private Color m_OriginalAmbientColour;
private float m_OriginalAmbientIntensity;
private readonly List<GameObject> m_Lights = new();
private TrembleSyncSettings m_Settings;
private struct TexturePair
{
public Texture2D Texture;
public RenderTexture RenderTexture;
}
public void Init(TrembleSyncSettings settings)
{
m_Settings = settings;
SetupGameObjects(m_Settings.MaterialCaptureLightIntensity, m_Settings.MaterialCaptureLightAngle);
List<Vector2Int> resolutions = new()
{
m_Settings.MaterialExportSize.ToVector2IntSize()
};
foreach (Material material in m_Settings.GetMaterialGroupsOrDefault().SelectMany(mg => mg.Materials))
{
MaterialRenderData renderData = m_Settings.GetMaterialRenderData(material);
Vector2Int dataRes = new(renderData.ResolutionX.ToIntSize(), renderData.ResolutionY.ToIntSize());
if (!resolutions.Contains(dataRes))
resolutions.Add(dataRes);
if (!material.TryGetMainTex(out Texture2D tex))
continue;
Vector2Int res = new(tex.width, tex.height);
if (!resolutions.Contains(res))
resolutions.Add(res);
}
foreach (Vector2Int res in resolutions)
{
GetOrCreateTexturePair(res);
}
// Move far away!
m_SceneRoot.transform.position = Vector3.up * 10000f;
// Set up lighting
m_OriginalAmbientMode = RenderSettings.ambientMode;
m_OriginalAmbientColour = RenderSettings.ambientLight;
m_OriginalAmbientIntensity = RenderSettings.ambientIntensity;
#if UNITY_6000_5_OR_NEWER
foreach (Light foundLight in GameObject.FindObjectsByType<Light>(FindObjectsInactive.Exclude))
#elif UNITY_2022_1_OR_NEWER
foreach (Light foundLight in GameObject.FindObjectsByType<Light>(FindObjectsInactive.Exclude, FindObjectsSortMode.None))
#else
foreach (Light foundLight in GameObject.FindObjectsOfType<Light>(false))
#endif
{
if (!foundLight.gameObject.activeSelf || foundLight == m_StudioLight)
continue;
m_Lights.Add(foundLight.gameObject);
foundLight.gameObject.SetActive(false);
}
RenderSettings.ambientMode = AmbientMode.Flat;
RenderSettings.ambientLight = Color.white;
}
public void DeInit()
{
GameObject.DestroyImmediate(m_SceneRoot);
if (m_RenderTextures.Values.Any(x => RenderTexture.active == x.RenderTexture))
{
RenderTexture.active = null;
}
// Release RT next frame - it seems if it's active above, it's not
// cleared/unset until next frame causing an error (:
foreach (TexturePair texPair in m_RenderTextures.Values)
{
#if UNITY_EDITOR
EditorApplication.delayCall += () =>
{
if (texPair.RenderTexture)
{
#endif
texPair.RenderTexture.Release();
#if UNITY_EDITOR
}
};
#endif
}
m_RenderTextures.Clear();
foreach (GameObject foundLight in m_Lights)
{
foundLight.SetActive(true);
}
m_Lights.Clear();
// Restore lighting
RenderSettings.ambientMode = m_OriginalAmbientMode;
RenderSettings.ambientLight = m_OriginalAmbientColour;
RenderSettings.ambientIntensity = m_OriginalAmbientIntensity;
}
private TexturePair GetOrCreateTexturePair(Vector2Int resolution)
{
if (!m_RenderTextures.TryGetValue(resolution, out TexturePair texturePair))
{
RenderTexture rt = new(resolution.x, resolution.y, 8, GraphicsFormat.R8G8B8A8_UNorm);
rt.Create();
Texture2D output = new(resolution.x, resolution.y, rt.graphicsFormat, TextureCreationFlags.None);
m_RenderTextures[resolution] = texturePair = new()
{
Texture = output,
RenderTexture = rt,
};
}
return texturePair;
}
public void SnapshotMaterial(Material m, string outPath)
{
MaterialRenderData renderData = m_Settings.GetMaterialRenderData(m);
Vector2Int res = renderData.GetResolutionOrDefault();
Texture2D mainTex = null;
if (renderData.ExportMode == MaterialImportMode.Dynamic)
{
if (!m.TryGetMainTex(out mainTex))
{
renderData.ExportMode = MaterialImportMode.FixedSize;
Debug.LogWarning($"Couldn't find a Main Texture for material \"{m.name}\"," +
"falling back to legacy render mode." +
"Make sure the shader has a texture marked as [Main Texture] or" +
"a texture named _MainTex, _BaseColor, or _Color.");
}
if (mainTex && !renderData.IsResolutionOverridden)
{
res = new(mainTex.width, mainTex.height);
}
}
TexturePair tex = GetOrCreateTexturePair(res);
// Do not remove - these are used in HDRP
bool createdTempMaterial = false;
Material tempMaterial = null;
switch (renderData.ExportMode)
{
case MaterialImportMode.FixedSize:
// Set up RT and material - we have to save it for HDRP
createdTempMaterial = TryCreateUnlitCopy(m, out tempMaterial, renderData.ExportMode);
#if HDRP_INSTALLED && UNITY_EDITOR
if (createdTempMaterial)
{
string tempName = Path.GetFileNameWithoutExtension(AssetDatabase.GetAssetPath(m)) + "_TrembleTemp.mat";
string tempPath = Path.Combine("Assets", tempName);
AssetDatabase.CreateAsset(tempMaterial, tempPath);
}
#endif
m_Renderer.material = tempMaterial ? tempMaterial : m;
// Render once
m_Camera.targetTexture = tex.RenderTexture;
m_Camera.Render();
if (RenderTexture.active != tex.RenderTexture)
RenderTexture.active = tex.RenderTexture;
// Copy texture from RT
tex.Texture.ReadPixels(new(0, 0, res.x, res.y), 0, 0);
File.WriteAllBytes(outPath, tex.Texture.EncodeToPNG());
break;
case MaterialImportMode.Dynamic:
#if UNITY_EDITOR
string path = AssetDatabase.GetAssetPath(mainTex);
//TODO(jwf): colourise by main colour?
// Is it a PNG, and we're not overriding resolution? Just copy it directly
if (!renderData.IsResolutionOverridden && path.EndsWithInvariant(".png"))
{
File.Copy(Application.dataPath + path.Remove(0, 6), outPath, true);
return;
}
#endif
// Else we make a copy to resize
m_Camera.targetTexture = null;
GL.sRGBWrite = true;
Graphics.Blit(mainTex, tex.RenderTexture);
RenderTexture.active = tex.RenderTexture;
// Copy texture from RT
tex.Texture.ReadPixels(new(0, 0, res.x, res.y), 0, 0);
File.WriteAllBytes(outPath, tex.Texture.EncodeToPNG());
break;
}
// Delete temp material, if we made one
#if HDRP_INSTALLED && UNITY_EDITOR
if (createdTempMaterial)
{
AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(tempMaterial));
}
#endif
}
private static readonly Dictionary<string, string> s_ShaderRemaps = new()
{
{ "Standard", "Legacy Shaders/Diffuse" }, // or Particles/Standard Unlit?
{ "Universal Render Pipeline/Lit", "Universal Render Pipeline/Unlit" },
{ "HDRP/Lit", "HDRP/Unlit" }
};
private bool TryCreateUnlitCopy(Material material, out Material newMaterial, MaterialImportMode mode)
{
// No lights in the scene. Assume we don't need to tweak materials as they're using ambient colour
if (mode is MaterialImportMode.Dynamic || (m_Lights.Count == 0 && mode is MaterialImportMode.FixedSize))
{
newMaterial = material;
return false;
}
#if UNITY_EDITOR
foreach ((string originalShader, string remapShader) in s_ShaderRemaps)
{
if (!material.shader.name.Equals(originalShader))
continue;
newMaterial = mode is MaterialImportMode.FixedSize ? new(Shader.Find(remapShader)) : new(Shader.Find(originalShader));
CopyMaterialProperties_Recursive(material, newMaterial);
// Ensure colours/textures set properly
newMaterial.color = material.color;
newMaterial.mainTexture = material.mainTexture;
// Render after post-processing (mostly for HDRP)
newMaterial.renderQueue = 2501;
return true;
}
#endif
newMaterial = material;
return false;
}
[Conditional("UNITY_EDITOR")]
private static void CopyMaterialProperties_Recursive(Material from, Material to)
{
#if UNITY_EDITOR
#if UNITY_2022_1_OR_NEWER
if (from.parent)
{
// If parent, copy that one first!
CopyMaterialProperties_Recursive(from.parent, to);
}
#endif
// Copy shader keywords
to.shaderKeywords = from.shaderKeywords;
// Iterate over all properties of the material
Shader shader = from.shader;
#if UNITY_6000_5_OR_NEWER
int propertyCount = shader.GetPropertyCount();
for (int i = 0; i < propertyCount; i++)
{
// Get the property type and name
ShaderPropertyType propertyType = shader.GetPropertyType(i);
string propertyName = shader.GetPropertyName(i);
// Copy the property value based on its type
switch (propertyType)
{
case ShaderPropertyType.Color:
to.SetColor(propertyName, from.GetColor(propertyName));
break;
case ShaderPropertyType.Vector:
to.SetVector(propertyName, from.GetVector(propertyName));
break;
case ShaderPropertyType.Float:
case ShaderPropertyType.Range:
to.SetFloat(propertyName, from.GetFloat(propertyName));
break;
case ShaderPropertyType.Texture:
to.SetTexture(propertyName, from.GetTexture(propertyName));
to.SetTextureOffset(propertyName, from.GetTextureOffset(propertyName));
to.SetTextureScale(propertyName, from.GetTextureScale(propertyName));
break;
}
}
#else
int propertyCount = ShaderUtil.GetPropertyCount(shader);
for (int i = 0; i < propertyCount; i++)
{
// Get the property type and name
ShaderUtil.ShaderPropertyType propertyType = ShaderUtil.GetPropertyType(shader, i);
string propertyName = ShaderUtil.GetPropertyName(shader, i);
// Copy the property value based on its type
switch (propertyType)
{
case ShaderUtil.ShaderPropertyType.Color:
to.SetColor(propertyName, from.GetColor(propertyName));
break;
case ShaderUtil.ShaderPropertyType.Vector:
to.SetVector(propertyName, from.GetVector(propertyName));
break;
case ShaderUtil.ShaderPropertyType.Float:
case ShaderUtil.ShaderPropertyType.Range:
to.SetFloat(propertyName, from.GetFloat(propertyName));
break;
case ShaderUtil.ShaderPropertyType.TexEnv:
to.SetTexture(propertyName, from.GetTexture(propertyName));
to.SetTextureOffset(propertyName, from.GetTextureOffset(propertyName));
to.SetTextureScale(propertyName, from.GetTextureScale(propertyName));
break;
}
}
#endif
// Copy render queue and other settings
to.renderQueue = from.renderQueue;
to.enableInstancing = from.enableInstancing;
to.doubleSidedGI = from.doubleSidedGI;
#endif
}
private void SetupGameObjects(float lightIntensity, float lightAngle)
{
m_SceneRoot = new("Scene Root");
// Set up gameobjects
GameObject snapCamera = new("Camera");
snapCamera.transform.SetParent(m_SceneRoot.transform, false);
snapCamera.transform.localPosition = new(0f, 0f, -0.1f);
m_Camera = snapCamera.AddComponent<Camera>();
m_Camera.orthographic = true;
m_Camera.allowHDR = false;
m_Camera.orthographicSize = 0.5f;
m_Camera.nearClipPlane = 0.01f;
m_Camera.farClipPlane = 0.5f;
m_Camera.backgroundColor = Color.black;
m_Camera.clearFlags = CameraClearFlags.Color;
Vector3 lightDirection = Vector3.Lerp(Vector3.right, Vector3.forward, lightAngle);
GameObject snapLight = new("Light");
snapLight.transform.SetParent(m_SceneRoot.transform, false);
snapLight.transform.localPosition = new(0f, 0f, -0.1f);
snapLight.transform.rotation = Quaternion.LookRotation(lightDirection);
m_StudioLight = snapLight.AddComponent<Light>();
m_StudioLight.type = LightType.Directional;
m_StudioLight.intensity = lightIntensity;
m_StudioLight.shadows = LightShadows.None;
m_StudioLight.color = Color.white;
GameObject snapQuad = new("Quad");
snapQuad.transform.SetParent(m_SceneRoot.transform, false);
snapQuad.transform.localPosition = Vector3.zero;
MeshFilter meshFilter = snapQuad.AddComponent<MeshFilter>();
meshFilter.sharedMesh = CreateQuadMesh();
m_Renderer = snapQuad.AddComponent<MeshRenderer>();
}
private Mesh CreateQuadMesh()
{
Mesh quadMesh = new()
{
name = "Quad",
vertices = new Vector3[]
{
new(-0.5f, -0.5f, 0f),
new(-0.5f, +0.5f, 0f),
new(+0.5f, +0.5f, 0f),
new(+0.5f, -0.5f, 0f),
},
normals = new Vector3[]
{
new(0f, 0f, -1f),
new(0f, 0f, -1f),
new(0f, 0f, -1f),
new(0f, 0f, -1f),
},
uv = new Vector2[]
{
new(0f, 0f),
new(0f, 1f),
new(1f, 1f),
new(1f, 0f),
},
};
List<int> indices = new() { 0, 1, 2, 0, 2, 3 };
quadMesh.SetIndices(indices, MeshTopology.Triangles, 0);
return quadMesh;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: cd6405c6b01049ba9a15b8f11f80f46d
timeCreated: 1707139093
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/MapTextureSnapshotter.cs
uploadId: 939664
+451
View File
@@ -0,0 +1,451 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Rendering;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace TinyGoose.Tremble
{
public class MeshBuilder
{
public delegate Material FindMaterialFunc(string name);
// -----------------------------------------------------------------------------------------------------------------------------
// Setup
// -----------------------------------------------------------------------------------------------------------------------------
private readonly TrembleMapImportSettings m_ImportSettings;
private readonly string m_WorldspawnName;
private readonly float m_SmoothingAngle;
private readonly float m_MaxMeshSurfaceArea;
private readonly FindMaterialFunc m_MaterialFinder;
// -----------------------------------------------------------------------------------------------------------------------------
// State
// -----------------------------------------------------------------------------------------------------------------------------
private int m_MeshCounter;
private readonly List<int> m_FaceIndicesBuffer = new();
private readonly List<BspMeshVertex> m_MeshVerticesBuffer = new(2048);
private readonly List<BspSubMesh> m_MeshSubmeshesBuffer = new(16);
private readonly Dictionary<int, int> m_BaseIndexToIndex = new(2048);
public MeshBuilder(TrembleMapImportSettings importSettings, FindMaterialFunc materialFinder, string worldspawnName, float smoothingAngle, float maxMeshSurfaceArea)
{
m_ImportSettings = importSettings;
m_WorldspawnName = worldspawnName;
m_SmoothingAngle = smoothingAngle;
m_MaxMeshSurfaceArea = maxMeshSurfaceArea;
m_MaterialFinder = materialFinder;
}
private static readonly HashSet<int> s_InnerFaceList = new(2048);
public void BuildMesh_Recursive(MapBsp mapBsp, Transform parent, int nodeID, HashSet<int> faceList, float importScale, int unityLayer, List<GameObject> intoListOrNull = null)
{
// Not a leaf - recurse unless smol
if (nodeID >= 0)
{
bool subdivide = TrembleSyncSettings.Get().SubdivideAllLeafs;
ref BspNode node = ref mapBsp.Nodes[nodeID];
if (!subdivide)
{
float sqrSurfArea = 0;
BspParser.GetSurfaceAreaSqr(mapBsp, node, ref sqrSurfArea, faceList);
subdivide = (sqrSurfArea > m_MaxMeshSurfaceArea * m_MaxMeshSurfaceArea);
}
if (subdivide)
{
s_InnerFaceList.Clear();
BuildMesh_Recursive(mapBsp, parent, node.Child1, s_InnerFaceList, importScale, unityLayer, intoListOrNull);
BuildMesh_Recursive(mapBsp, parent, node.Child2, s_InnerFaceList, importScale, unityLayer, intoListOrNull);
// Don't build this one
return;
}
}
// This is a leaf, or as small as we want to go - create!
Bounds bounds = new();
if (nodeID < 0)
{
bounds.min = new Vector3(mapBsp.Leaves[-nodeID - 1].Min.x, mapBsp.Leaves[-nodeID - 1].Min.z, mapBsp.Leaves[-nodeID - 1].Min.y) * importScale;
bounds.max = new Vector3(mapBsp.Leaves[-nodeID - 1].Max.x, mapBsp.Leaves[-nodeID - 1].Max.z, mapBsp.Leaves[-nodeID - 1].Max.y) * importScale;
}
else
{
bounds.min = new Vector3(mapBsp.Nodes[nodeID].Min.x, mapBsp.Nodes[nodeID].Min.z, mapBsp.Nodes[nodeID].Min.y) * importScale;
bounds.max = new Vector3(mapBsp.Nodes[nodeID].Max.x, mapBsp.Nodes[nodeID].Max.z, mapBsp.Nodes[nodeID].Max.y) * importScale;
}
GameObject meshGo = BuildMesh(mapBsp, parent, nodeID, true, m_WorldspawnName, true, bounds.center, true);
if (meshGo)
{
meshGo.layer = unityLayer;
intoListOrNull?.Add(meshGo);
}
}
public GameObject BuildMesh(MapBsp mapBsp, Transform parent, int sourceID, bool isBspRoot, string modelName, bool isFromNode, Vector3 origin, bool isStatic, Quaternion? rotation = null, float smoothingAngleOverride = -1f)
{
// Thank you again John Evans. This method would have been awful to figure out by myself!
// Collect face indices
m_FaceIndicesBuffer.Clear();
if (isFromNode)
{
if (sourceID < 0)
{
BspParser.GetFaceList(mapBsp, mapBsp.Leaves[-sourceID - 1], m_FaceIndicesBuffer);
}
else
{
BspParser.GetFaceList(mapBsp, mapBsp.Nodes[sourceID], m_FaceIndicesBuffer);
}
}
else
{
for (int i = 0; i < mapBsp.Models[sourceID].NumFaces; i++)
{
m_FaceIndicesBuffer.Add(mapBsp.Models[sourceID].StartFaceIdx + i);
}
}
if (m_FaceIndicesBuffer.Count == 0)
return null;
// Gather vertices and submeshes
m_MeshVerticesBuffer.Clear();
m_MeshSubmeshesBuffer.Clear();
m_BaseIndexToIndex.Clear();
// Get faces for this mesh
foreach (BspFace face in m_FaceIndicesBuffer.Select(faceIdx => mapBsp.Faces[faceIdx]))
{
// Get each vertex for this face
for (int faceVertIdx = face.MeshVertexStartIdx; faceVertIdx < face.MeshVertexStartIdx + face.NumMeshVertices; faceVertIdx++)
{
int baseIdx = face.VertexStartIdx + mapBsp.MeshVertices[faceVertIdx];
// Find or add a vertex to this mesh
int localVertIndex = FindOrAddVertexForBaseIndex(baseIdx, mapBsp, origin, rotation);
AddVertexToSubmeshWithTexID(m_MeshSubmeshesBuffer, face.TexID, localVertIndex);
}
}
// Discover and apply materials
List<Material> meshMaterials = new(m_MeshSubmeshesBuffer.Count);
{
for (int materialIdx = 0; materialIdx < m_MeshSubmeshesBuffer.Count; materialIdx++)
{
string matName = mapBsp.TexInfos[m_MeshSubmeshesBuffer[materialIdx].TexID].Name;
// Skip? Skip!
if (matName.ContainsInvariant(TBConsts.SKIP_TEXTURE, caseSensitive: true))
{
m_MeshSubmeshesBuffer.RemoveAt(materialIdx);
materialIdx--;
continue;
}
Material foundMaterial = m_MaterialFinder.Invoke(matName);
meshMaterials.Add(foundMaterial);
if (foundMaterial)
{
m_ImportSettings.AddDependency(foundMaterial);
}
}
}
// Create Unity mesh
Mesh mesh = CreateUnityMesh(isBspRoot, ref modelName, m_MeshVerticesBuffer, m_MeshSubmeshesBuffer, smoothingAngleOverride);
bool isTinyMesh = mesh.triangles.Length == 0 || Mathf.Max(mesh.bounds.extents.x, mesh.bounds.extents.y, mesh.bounds.extents.z) < 0.1f;
if (isTinyMesh)
return null;
m_ImportSettings.SaveObjectInMap(mesh.name, mesh);
// Create model GameObject and attach mesh
GameObject modelObject = new(modelName);
modelObject.transform.parent = parent;
modelObject.transform.position = origin;
modelObject.AddComponent<MeshFilter>().mesh = mesh;
modelObject.AddComponent<MeshRenderer>().sharedMaterials = meshMaterials.ToArray();
TrembleSyncSettings syncSettings = TrembleSyncSettings.Get();
// Generate mesh collider
Mesh collisionMesh;
if (syncSettings.PipelineSimplifyCollisionMeshes)
{
using TrembleTimerScope _ = new(TrembleTimer.Context.SimplifyCollisionMeshes);
collisionMesh = GenerateSimplifiedUnityCollisionMesh(mesh);
}
else
{
collisionMesh = Mesh.Instantiate(mesh);
}
collisionMesh.name = mesh.name.Replace("_mesh", "_collision");
m_ImportSettings.SaveObjectInMap(collisionMesh.name, collisionMesh);
modelObject.AddComponent<MeshCollider>().sharedMesh = collisionMesh;
// Calculate lightmap UVs
#if UNITY_EDITOR
if (isStatic && syncSettings.PipelineUnwrapUV2)
{
using TrembleTimerScope _ = new(TrembleTimer.Context.GenerateUV2);
Unwrapping.GenerateSecondaryUVSet(mesh, new()
{
hardAngle = 70.0f,
angleError = 8.0f,
areaError = 15.0f,
packMargin = mapBsp.Options.UV2Padding,
});
}
#endif
modelObject.isStatic = isStatic;
return modelObject;
}
private Mesh CreateUnityMesh(bool isBspRoot, ref string modelName, List<BspMeshVertex> meshVertices, List<BspSubMesh> meshSubmeshes, float smoothingAngleOverride)
{
IndexFormat meshFormat = IndexFormat.UInt16;
foreach (BspSubMesh submesh in meshSubmeshes)
{
if (submesh.Triangles.Count * 3 > 65535)
{
Debug.Log($"Note: Mesh '{modelName}' using 32-bit indices due to high index count. Render performance may be reduced!");
meshFormat = IndexFormat.UInt32;
break;
}
}
// Create Unity mesh
Mesh mesh = new()
{
indexFormat = meshFormat,
subMeshCount = meshSubmeshes.Count,
vertices = meshVertices.Select(mv => mv.Position).ToArray(),
normals = meshVertices.Select(mv => mv.Normal).ToArray(),
uv = meshVertices.Select(mv => mv.UV).ToArray(),
};
for (int i = 0; i < meshSubmeshes.Count; i++)
{
mesh.SetTriangles(meshSubmeshes[i].Triangles.ToArray(), i);
}
mesh.RecalculateBounds();
mesh.RecalculateTangents();
// Name the mesh
if (isBspRoot)
{
modelName += $"_{++m_MeshCounter}";
mesh.name = $"{modelName}_mesh";
}
else
{
mesh.name = $"{modelName}_{++m_MeshCounter}_mesh";
}
float smoothingAngle = m_SmoothingAngle;
if (smoothingAngleOverride >= 0f)
smoothingAngle = smoothingAngleOverride;
// Smooth out normals if required
if (smoothingAngle > 0 && TrembleSyncSettings.Get().PipelineSmoothMeshNormals)
{
using TrembleTimerScope _ = new(TrembleTimer.Context.SmoothMeshNormals);
SmoothMeshNormals(mesh, smoothingAngle);
}
return mesh;
}
private int FindOrAddVertexForBaseIndex(int baseIdx, MapBsp mapBsp, Vector3 origin, Quaternion? rotation)
{
// Find existing value
if (m_BaseIndexToIndex.TryGetValue(baseIdx, out int currentMeshVertIdx))
return currentMeshVertIdx;
Vector3 position = mapBsp.Vertices[baseIdx].Position - origin;
Vector3 normal = mapBsp.Vertices[baseIdx].Normal;
if (rotation != null)
{
position = rotation.Value * position;
normal = rotation.Value * normal;
}
// Not found, add a new one
BspMeshVertex currentMeshVertex = new()
{
Position = position,
Normal = normal,
UV = mapBsp.Vertices[baseIdx].TexCoord + new Vector2(0.5f, 0.5f),
BaseIndex = baseIdx
};
m_MeshVerticesBuffer.Add(currentMeshVertex);
m_BaseIndexToIndex.Add(baseIdx, m_MeshVerticesBuffer.Count - 1);
return m_MeshVerticesBuffer.Count - 1;
}
private static void AddVertexToSubmeshWithTexID(List<BspSubMesh> meshSubmeshes, int texID, int localVertexIdx)
{
// Find a submesh that uses this texture ID
foreach (BspSubMesh subMesh in meshSubmeshes)
{
if (texID != subMesh.TexID)
continue;
subMesh.Triangles.Add(localVertexIdx);
return;
}
// Not found, make a new submesh for this face
meshSubmeshes.Add(new()
{
TexID = texID,
Triangles = new() { localVertexIdx },
});
}
// -----------------------------------------------------------------------------------------------------------------------------
// Utilities
// -----------------------------------------------------------------------------------------------------------------------------
private static void SmoothMeshNormals(Mesh mesh, float maxAngle = 45.0f)
{
Vector3[] newVertices = mesh.vertices;
Vector3[] newNormals = mesh.normals;
Vector3[] originalNormals = new Vector3[newNormals.Length];
Array.Copy(newNormals, originalNormals, newNormals.Length);
float cosThreshold = Mathf.Cos(Mathf.Deg2Rad * maxAngle);
for (int i = 0; i < newVertices.Length; i++)
{
int groupCount = 1;
Vector3 currentNormal = originalNormals[i];
for (int j = 0; j < newVertices.Length; j++)
{
if (i == j)
continue;
// Inlined SqrMagnitude to avoid Vector3 .ctor
ref Vector3 from = ref newVertices[i];
ref Vector3 to = ref newVertices[j];
float xDiff = from.x - to.x;
float yDiff = from.y - to.y;
float zDiff = from.z - to.z;
float sqrDistance = xDiff * xDiff + yDiff * yDiff + zDiff * zDiff;
if (sqrDistance >= 0.01f * 0.01f)
continue;
// Merge verts!
to = from;
// Also inline AngleBetween and avoid Acos for speed up... these 3 changes reduce from 700ms to about 20ms ;)
float cosBetween = Vector3.Dot(originalNormals[i], originalNormals[j]);
if (cosBetween > cosThreshold)
{
currentNormal += originalNormals[j];
groupCount++;
}
}
if (groupCount >= 2)
{
newNormals[i] = currentNormal / groupCount;
}
}
mesh.vertices = newVertices;
mesh.normals = newNormals;
mesh.RecalculateBounds();
mesh.RecalculateTangents();
}
private static Mesh GenerateSimplifiedUnityCollisionMesh(Mesh inputMesh, float threshold = 0.01f)
{
Mesh outputMesh = new() { name = inputMesh.name};
Vector3[] inputVertices = inputMesh.vertices;
int inputVertexCount = inputMesh.vertexCount;
int[] inputTriangles = inputMesh.triangles;
int[] vertexRemap = new int[inputVertexCount];
List<Vector3> newVertices = new() { inputVertices[0] };
float sqrThreshold = threshold * threshold;
for (int inputVertIdx = 0; inputVertIdx < inputVertexCount; inputVertIdx++)
{
bool wasRemapped = false;
float newVertCount = newVertices.Count;
for (int newVertIdx = 0; newVertIdx < newVertCount; newVertIdx++)
{
Vector3 from = inputVertices[inputVertIdx];
Vector3 to = newVertices[newVertIdx];
float xDiff = from.x - to.x;
float yDiff = from.y - to.y;
float zDiff = from.z - to.z;
float sqrDistance = xDiff * xDiff + yDiff * yDiff + zDiff * zDiff;
if (sqrDistance > sqrThreshold)
continue;
vertexRemap[inputVertIdx] = newVertIdx;
wasRemapped = true;
break;
}
if (!wasRemapped)
{
newVertices.Add(inputVertices[inputVertIdx]);
vertexRemap[inputVertIdx] = newVertices.Count - 1;
}
}
// Create triangles array
int[] newTriangles = new int[inputTriangles.Length];
int triPtr = 0;
for (int i = 0; i < inputTriangles.Length; i += 3)
{
if (vertexRemap[inputTriangles[i]] == vertexRemap[inputTriangles[i + 1]] || vertexRemap[inputTriangles[i]] == vertexRemap[inputTriangles[i + 2]] || vertexRemap[inputTriangles[i + 1]] == vertexRemap[inputTriangles[i + 2]])
continue;
newTriangles[triPtr++] = vertexRemap[inputTriangles[i + 0]];
newTriangles[triPtr++] = vertexRemap[inputTriangles[i + 1]];
newTriangles[triPtr++] = vertexRemap[inputTriangles[i + 2]];
}
outputMesh.vertices = newVertices.ToArray();
outputMesh.triangles = newTriangles;
return outputMesh;
}
}
}
+10
View File
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 61078239862345b09915cb704095f22d
timeCreated: 1709824324
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/MeshBuilder.cs
uploadId: 939664
@@ -0,0 +1,157 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Text;
namespace TinyGoose.Tremble
{
public static class NamingConventionUtil
{
public static string ToNamingConvention(this string input, NamingConvention convention)
{
switch (convention)
{
case NamingConvention.SnakeCase:
return input.ToSnakeCase();
case NamingConvention.UpperCamelCase:
return CapitaliseFirstLetter(input, true);
case NamingConvention.LowerCamelCase:
return CapitaliseFirstLetter(input, false);
case NamingConvention.HumanFriendly:
return ToHuman(input);
case NamingConvention.PreserveExact:
return input;
}
return input;
}
private static readonly StringBuilder s_SnakeBuilder = new(128);
private static string ToSnakeCase(this string inString)
{
s_SnakeBuilder.Clear();
for (int i = 0; i < inString.Length; i++)
{
if (inString[i] == ' ')
{
s_SnakeBuilder.Append('_');
continue;
}
if (Char.IsUpper(inString[i]) && i > 0)
{
// Not if we just output an underscore!
if (s_SnakeBuilder[^1] != '_')
{
// If the previous character wasn't uppercase
if (!Char.IsUpper(inString[i - 1]))
{
s_SnakeBuilder.Append('_');
}
// Or the next character isn't uppercase
else if (i < inString.Length - 1 && Char.IsLower(inString[i + 1]))
{
s_SnakeBuilder.Append('_');
}
}
}
s_SnakeBuilder.Append(Char.ToLowerInvariant(inString[i]));
}
return s_SnakeBuilder.ToString();
}
private static string ToHuman(this string inString)
{
s_SnakeBuilder.Clear();
int firstIndex = inString.IndexOf('_') + 1;
for (int i = firstIndex; i < inString.Length; i++)
{
// Straight up convert underscores to spaces
if (inString[i] == '_')
{
s_SnakeBuilder.Append(' ');
continue;
}
if (Char.IsUpper(inString[i]) && i > firstIndex)
{
// Not if we just output a space!
if (s_SnakeBuilder[^1] != ' ' && s_SnakeBuilder[^1] != '_')
{
// If the previous character wasn't uppercase
if (!Char.IsUpper(inString[i - 1]))
{
s_SnakeBuilder.Append(' ');
}
// Or the next character isn't uppercase
else if (i < inString.Length - 1 && Char.IsLower(inString[i + 1]))
{
s_SnakeBuilder.Append(' ');
}
}
}
if (i == firstIndex || s_SnakeBuilder[^1] == ' ')
{
s_SnakeBuilder.Append(Char.ToUpperInvariant(inString[i]));
}
else
{
s_SnakeBuilder.Append(Char.ToLowerInvariant(inString[i]));
}
}
return s_SnakeBuilder.ToString();
}
public static string FromHuman(ReadOnlySpan<char> inString)
{
s_SnakeBuilder.Clear();
int inBrackets = 0;
for (int i = 0; i < inString.Length; i++)
{
if (inString[i] == '(') { inBrackets++; continue; }
if (inString[i] == ')') { inBrackets--; continue; }
if (!Char.IsLetter(inString[i]) && !Char.IsNumber(inString[i]) && inString[i] != '_')
continue;
if (inBrackets > 0)
continue;
if (i == 0 || inString[i - 1] == ' ')
{
s_SnakeBuilder.Append(Char.ToUpperInvariant(inString[i]));
}
else
{
s_SnakeBuilder.Append(inString[i]);
}
}
return s_SnakeBuilder.ToString().Trim();
}
public static string FromHuman(string inString) => FromHuman(inString.AsSpan());
private static string CapitaliseFirstLetter(string input, bool capitalise)
{
if (input.IsNullOrEmpty())
return "";
if (input.Length == 1)
return capitalise ? input.ToUpperInvariant() : input.ToLowerInvariant();
return (capitalise ? Char.ToUpperInvariant(input[0]) : Char.ToLowerInvariant(input[0]))
+ input.Substring(1);
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 795d7b8deadc141e6a5a75234bb0b0b8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/NamingConventionUtil.cs
uploadId: 939664
@@ -0,0 +1,49 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.IO;
using System.Threading.Tasks;
using UnityEngine;
namespace TinyGoose.Tremble
{
public static class PackedResourceUtil
{
#if UNITY_EDITOR
public static async Task<string> PackFolderIntoResource(string folderPath, string resourceName)
{
if (!Directory.Exists(folderPath))
return null;
string containingFolder = Path.Combine(Directory.GetCurrentDirectory(), "Assets", "StreamingAssets");
string resourcePath = Path.Combine(containingFolder, resourceName + ".tpak");
DirectoryUtil.CreateAllDirectories(containingFolder);
File.Delete(resourcePath);
TpakArchive tpak = new(resourcePath);
tpak.AddDirectory(folderPath);
await tpak.PackAsync();
return resourcePath;
}
#endif
public static async Task<bool> UnpackResourceIntoFolder(string resourceName, string folderPath)
{
// Empty directory
DirectoryUtil.CreateAllDirectories(folderPath);
string resourcePath = Path.Combine(Application.streamingAssetsPath, resourceName + ".tpak");
if (!File.Exists(resourcePath))
return false;
TpakArchive tpak = new(resourcePath);
await tpak.UnpackAsync(folderPath);
return true;
}
}
}
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 309d4c10ad944b18a2e61762c8125acc
timeCreated: 1715783554
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/PackedResourceUtil.cs
uploadId: 939664
+27
View File
@@ -0,0 +1,27 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace TinyGoose.Tremble
{
public static class PrefabUtil
{
public static GameObject InstantiatePrefab(GameObject prefab, Transform parent = null)
{
#if UNITY_EDITOR
return Application.isPlaying
? GameObject.Instantiate(prefab, parent)
: (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent);
#else
return GameObject.Instantiate(prefab, parent);
#endif
}
}
}
+10
View File
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: d9d995248a5748abbedb56c4af36f30b
timeCreated: 1715879939
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/PrefabUtil.cs
uploadId: 939664
+71
View File
@@ -0,0 +1,71 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
//#define USE_SYNC_PROGRESS
using UnityEditor;
namespace TinyGoose.Tremble
{
public static class ProgressUtil
{
public static int Start(string taskName)
{
#if UNITY_EDITOR
#if USE_SYNC_PROGRESS
EditorUtility.DisplayProgressBar("Trembling...", taskName, 0f);
return -1;
#else
return Progress.Start(taskName, options: Progress.Options.Managed | Progress.Options.Sticky);
#endif
#else
return -1;
#endif
}
public static void Succeed(int task)
{
#if UNITY_EDITOR
#if USE_SYNC_PROGRESS
EditorUtility.DisplayProgressBar("Trembling...", "done", 1f);
EditorUtility.ClearProgressBar();
#else
if (task == 0)
return;
Progress.Finish(task);
#endif
#endif
}
public static void Fail(int task)
{
#if UNITY_EDITOR
#if USE_SYNC_PROGRESS
EditorUtility.ClearProgressBar();
#else
if (task == 0)
return;
Progress.Finish(task, Progress.Status.Failed);
#endif
#endif
}
public static void Report(int task, int step, int totalSteps, string description)
{
#if UNITY_EDITOR
#if USE_SYNC_PROGRESS
float progress = (float)step / (float)totalSteps;
EditorUtility.DisplayProgressBar("Tremble", $"{description} ({step + 1}/{totalSteps} - {progress*100:F0}%)", progress);
#else
if (task == 0)
return;
float progress = (float)step / (float)totalSteps;
Progress.Report(task, step, totalSteps, $"{description} ({step + 1}/{totalSteps} - {progress*100:F0}%)");
#endif
#endif
}
}
}
+10
View File
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 0bf65aab508f4d5a99f9f050ab34c9fd
timeCreated: 1715667825
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/ProgressUtil.cs
uploadId: 939664
+20
View File
@@ -0,0 +1,20 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.IO;
namespace TinyGoose.Tremble
{
public static class StreamWriterUtil
{
public static void WriteIndent(this StreamWriter sw, int indent)
{
for (int i = 0; i < indent; i++)
{
sw.Write('\t');
}
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: 7fe95984a548d45aca3824cfa3d3d8e0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/StreamWriterUtil.cs
uploadId: 939664
+131
View File
@@ -0,0 +1,131 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Collections.Generic;
using System.Text;
namespace TinyGoose.Tremble
{
public static class StringUtil
{
public static bool Split(this string input, char splitter, out string first, out string rest)
=> Split(input.AsSpan(), splitter, out first, out rest);
public static bool Split(this ReadOnlySpan<char> input, char splitter, out string first, out string rest)
{
bool insideQuotes = false;
int index = -1;
for (int c = 0; c < input.Length; c++)
{
if (input[c] == '"')
{
insideQuotes = !insideQuotes;
}
if (insideQuotes || input[c] != splitter)
continue;
index = c;
break;
}
if (index == -1)
{
first = null;
rest = input.ToString();
return false;
}
first = input[..index].ToString().Trim();
rest = input[(index + 1)..].ToString().Trim();
return true;
}
public static bool IsNullOrEmpty(this string input) => string.IsNullOrEmpty(input);
public static bool EqualsInvariant(this string input, string other, bool caseSensitive = false)
{
StringComparison compareType = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
return input.Equals(other, compareType);
}
public static bool ContainsInvariant(this string input, string other, bool caseSensitive = false)
{
StringComparison compareType = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
return input.Contains(other, compareType);
}
public static bool StartsWithInvariant(this string input, string other, bool caseSensitive = false)
{
StringComparison compareType = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
return input.StartsWith(other, compareType);
}
public static bool EndsWithInvariant(this string input, string other, bool caseSensitive = false)
{
StringComparison compareType = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
return input.EndsWith(other, compareType);
}
public static int IndexOfInvariant(this string input, string other, bool caseSensitive = false)
{
StringComparison compareType = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
return input.IndexOf(other, compareType);
}
public static int LastIndexOfInvariant(this string input, string other, bool caseSensitive = false)
{
StringComparison compareType = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
return input.LastIndexOf(other, compareType);
}
public static bool EqualsInvariant(this ReadOnlySpan<char> input, string other, bool caseSensitive = false)
{
StringComparison compareType = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
return input.Equals( other, compareType);
}
public static bool ContainsInvariant(this ReadOnlySpan<char> input, string other, bool caseSensitive = false)
{
StringComparison compareType = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
return input.Contains( other, compareType);
}
public static bool StartsWithInvariant(this ReadOnlySpan<char> input, string other, bool caseSensitive = false)
{
StringComparison compareType = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
return input.StartsWith( other, compareType);
}
public static bool EndsWithInvariant(this ReadOnlySpan<char> input, string other, bool caseSensitive = false)
{
StringComparison compareType = caseSensitive ? StringComparison.InvariantCulture : StringComparison.InvariantCultureIgnoreCase;
return input.EndsWith( other, compareType);
}
private static readonly HashSet<char> DEFAULT_ILLEGAL_CHARS = new() { '{', '}', ' ', '[', ']', '"', '\'', '.', ',' };
public static string Sanitise(this string input, HashSet<char> illegalCharacters = null, char? replacement = null)
{
if (input == null)
return null;
illegalCharacters ??= DEFAULT_ILLEGAL_CHARS;
StringBuilder newName = new(input.Length);
foreach (char c in input)
{
if (illegalCharacters.Contains(c))
{
if (replacement.HasValue)
{
newName.Append(replacement.Value);
}
continue;
}
newName.Append(c);
}
return newName.ToString();
}
}
}
+10
View File
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 3a1e2c6b1ca3410bae79a376273f1e2f
timeCreated: 1712132630
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/StringUtil.cs
uploadId: 939664
+201
View File
@@ -0,0 +1,201 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System.Linq;
using UnityEngine;
namespace TinyGoose.Tremble
{
public static class TextureUtil
{
public static Texture2D GenerateColourTexture(Color32 colour)
{
int size = TrembleSyncSettings.Get().MaterialExportSize.ToIntSize();
int sizeSqr = size * size;
Texture2D texture = new(size, size);
if (s_ColourBuffer == null || s_ColourBuffer.Length != sizeSqr)
{
s_ColourBuffer = new Color32[sizeSqr];
}
for (int i = 0; i < sizeSqr; i++)
{
s_ColourBuffer[i] = colour;
s_ColourBuffer[i].a = 0xFF;
}
texture.SetPixels32(s_ColourBuffer);
return texture;
}
public static Texture2D GenerateCheckerTexture(int checkerSize = 64) => GenerateCheckerTexture(Color.black, Color.white, checkerSize);
public static Texture2D GenerateCheckerTexture(Color32 colour1, Color32 colour2, int checkerSize = 64)
{
int size = TrembleSyncSettings.Get().MaterialExportSize.ToIntSize();
Texture2D texture = new(size, size);
texture.SetPixels32(GenerateCheckerColours(size, colour1, colour2, checkerSize));
return texture;
}
public static Texture2D GenerateClipTexture(Color32 colour1, Color32 colour2, Color32 clipText, int checkerSize = 64)
{
int size = TrembleSyncSettings.Get().MaterialExportSize.ToIntSize();
Texture2D texture = new(size, size);
Color32[] image = GenerateCheckerColours(size, colour1, colour2, checkerSize);
ApplyClipTexture(image, size, clipText, checkerSize);
texture.SetPixels32(image);
return texture;
}
public static Texture2D GenerateSkipTexture(Color32 colour1, Color32 colour2, Color32 clipText, int checkerSize = 64)
{
int size = TrembleSyncSettings.Get().MaterialExportSize.ToIntSize();
Texture2D texture = new(size, size);
Color32[] image = GenerateCheckerColours(size, colour1, colour2, checkerSize);
ApplySkipTexture(image, size, clipText, checkerSize);
texture.SetPixels32(image);
return texture;
}
private static Color32[] s_ColourBuffer;
private static Color32[] GenerateCheckerColours(int size, Color32 colour1, Color32 colour2, int checkerSize = 64)
{
int sizeSqr = size * size;
if (s_ColourBuffer == null || s_ColourBuffer.Length != sizeSqr)
{
s_ColourBuffer = new Color32[sizeSqr];
}
for (int x = 0; x < size; x++)
{
for (int y = 0; y < size; y++)
{
int xCoord = x / checkerSize;
int yCoord = y / checkerSize;
if ((xCoord + yCoord) % 2 == 0)
{
s_ColourBuffer[y * size + x] = colour1;
}
else
{
s_ColourBuffer[y * size + x] = colour2;
}
s_ColourBuffer[y * size + x].a = 0xFF;
}
}
return s_ColourBuffer;
}
// Cheeky clip texture
private const char _ = ' ';
private static readonly string[] s_ClipTexture =
{
"", "", // 2-line space
_ + "/--" + _ + "| " + _ + "|" + _ + "/-|" + _ + _ + _,
_ + "| " + _ + "| " + _ + " " + _ + "| |" + _ + _ + _,
_ + "| " + _ + "| " + _ + "|" + _ + "+_/" + _ + _ + _,
_ + "| " + _ + "| " + _ + "|" + _ + "| " + _ + _ + _,
_ + "|__" + _ + "|__" + _ + "|" + _ + "| " + _ + _ + _,
"", // 1-line space
};
private static readonly string[] s_SkipTexture =
{
"", "", // 2-line space
_ + "/--" + _ + "| |" + _ + "|" + _ + "/-|" + _ + _ + _,
_ + "| " + _ + "| /" + _ + " " + _ + "| |" + _ + _ + _,
_ + "---" + _ + "|/ " + _ + "|" + _ + "+-/" + _ + _ + _,
_ + " |" + _ + "|+ " + _ + "|" + _ + "| " + _ + _ + _,
_ + "--+" + _ + "| +" + _ + "|" + _ + "| " + _ + _ + _,
"", // 1-line space
};
private static void ApplyClipTexture(Color32[] image, int size, Color32 textColour, int checkerSize = 64)
=> ApplyWordTexture(image, s_ClipTexture, size, textColour, checkerSize);
private static void ApplySkipTexture(Color32[] image, int size, Color32 textColour, int checkerSize = 64)
=> ApplyWordTexture(image, s_SkipTexture, size, textColour, checkerSize);
private static void ApplyWordTexture(Color32[] image, string[] wordTexture, int size, Color32 textColour, int checkerSize = 64)
{
int clipWidth = 16;
float clipScale = (float)size / (float)checkerSize;
for (int x = 0; x < size; x++)
{
for (int y = 0; y < size; y++)
{
// Offset every other row
int offset = ((y / checkerSize) & 1) == 0 ? 0 : checkerSize;
// Scale the text
int scaledX = (int)((x + offset) / clipScale);
int scaledY = (int)(y / clipScale);
// Flip rows (textures, amirite?!)
int row = size - scaledY - 1;
// Pull the pixel data from the array
string wordRow = wordTexture[row % wordTexture.Length];
char wordValue = wordRow.Length > 0 ? wordRow[scaledX % clipWidth] : _;
if (wordValue != _)
{
// Write pixel if it's on
image[y * size + x] = textColour;
}
}
}
}
// Account for weird custom choices for main texture names
private static readonly int[] LIKELY_MAIN_TEXTURE_IDS =
{
Shader.PropertyToID("_BaseColor"),
Shader.PropertyToID("_Color"),
Shader.PropertyToID("_maintex"),
Shader.PropertyToID("_Maintex"),
};
public static bool TryGetMainTex(this Material material, out Texture2D mainTexture)
{
if (!material)
{
mainTexture = null;
return false;
}
// First check for _MainTex or [MainTexture]
mainTexture = material.mainTexture as Texture2D;
if (!mainTexture)
{
foreach (int name in LIKELY_MAIN_TEXTURE_IDS)
{
if (!material.HasTexture(name))
continue;
mainTexture = material.GetTexture(name) as Texture2D;
break;
}
}
return mainTexture;
}
}
}
+10
View File
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 90d10613740347268599188e50f56c1f
timeCreated: 1709808325
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/TextureUtil.cs
uploadId: 939664
+237
View File
@@ -0,0 +1,237 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace TinyGoose.Tremble
{
public static class TrenchBroomUtil
{
private static string TrenchBroomPathFromPrefs
{
#if UNITY_EDITOR
get => EditorPrefs.GetString("Tremble_TBPath", null);
set => EditorPrefs.SetString("Tremble_TBPath", value);
#else
get => PlayerPrefs.GetString("Tremble_TBPath", null);
set => PlayerPrefs.SetString("Tremble_TBPath", value);
#endif
}
public static string TrenchBroomPath
{
get
{
// First look for a running Trenchbroom
Process[] procs = Process.GetProcessesByName("trenchbroom");
if (procs.Length > 0 && procs[0].MainModule != null)
{
TrenchBroomPathFromPrefs = procs[0].MainModule.FileName;
return procs[0].MainModule.FileName;
}
// Not running - try a path we previously knew about
string path = TrenchBroomPathFromPrefs;
if (path != null && File.Exists(path))
return path;
return null;
}
private set => TrenchBroomPathFromPrefs = value;
}
public static bool IsTrenchBroomRunning
{
get
{
Process proc = Process.GetProcessesByName("trenchbroom").FirstOrDefault();
if (proc == null)
return false;
TrenchBroomPath = proc.MainModule.FileName;
return true;
}
}
public static bool DoesEnginePathExistInTrenchBroomPrefs()
{
string preferencesFile = Path.Combine(GetTrenchBroomConfigFolder(), "Preferences.json");
string key = $"Games/{TrembleConsts.GAME_NAME}/Path";
if (!File.Exists(preferencesFile))
return false;
// Read in current config
foreach (string line in File.ReadLines(preferencesFile))
{
string trimmedLine = line.Trim(' ', ',');
// Ignore braces and empty lines
if (trimmedLine.IsNullOrEmpty() || trimmedLine is "{" or "}")
continue;
// Existing path? - patch it!
if (trimmedLine.StartsWith($"\"{key}\""))
return true;
}
return false;
}
public static async Task<bool> WriteEnginePathIntoTrenchBroomPrefs()
{
// I didn't want to import a JSON library so - this is it.
// Read each line into an array (ignore the braces) and remove the commas.
// Find if there's a current path - if so, patch it up.
// if not, add a new one at the end.
string preferencesFile = Path.Combine(GetTrenchBroomConfigFolder(), "Preferences.json");
string key = $"Games/{TrembleConsts.GAME_NAME}/Path";
string projectFolderUnix = Directory.GetCurrentDirectory().Replace('\\', '/');
string entry = $"\"{key}\": \"{projectFolderUnix}\"";
bool wasPatched = false;
List<string> configLines = new();
if (File.Exists(preferencesFile))
{
// Read in current config
foreach (string line in File.ReadLines(preferencesFile))
{
string trimmedLine = line.Trim(' ', ',');
// Ignore braces and empty lines
if (trimmedLine.IsNullOrEmpty() || trimmedLine is "{" or "}")
continue;
// Existing path? - patch it!
if (trimmedLine.StartsWith($"\"{key}\""))
{
configLines.Add(entry);
wasPatched = true;
continue;
}
configLines.Add(trimmedLine);
}
}
if (!wasPatched)
{
// We didn't find the game - append to the end (before close brace)
configLines.Add(entry);
}
// Write out new config
for (int lineIdx = 0; lineIdx < configLines.Count; lineIdx++)
{
// Indent
configLines[lineIdx] = $" {configLines[lineIdx]}";
// Add commas (not on the last one!)
if (lineIdx < configLines.Count - 1)
{
configLines[lineIdx] += ",";
}
}
configLines.Insert(0, "{");
configLines.Add("}");
DirectoryUtil.CreateAllDirectories(preferencesFile);
await File.WriteAllLinesAsync(preferencesFile, configLines);
return !wasPatched;
}
private static string GetTrenchBroomConfigFolder() =>
Application.platform switch
{
RuntimePlatform.OSXEditor or RuntimePlatform.OSXPlayer
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support", "TrenchBroom"),
RuntimePlatform.WindowsEditor or RuntimePlatform.WindowsPlayer
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "TrenchBroom"),
RuntimePlatform.LinuxEditor or RuntimePlatform.LinuxPlayer
=> Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".TrenchBroom"),
_ => throw new ArgumentOutOfRangeException(nameof(Application.platform), "Unknown platform!")
};
public static string GetGameFolder() => Path.Combine(GetTrenchBroomConfigFolder(), "games", "Unity", Application.productName);
public static void OpenWithTrenchBroom(string mapFile)
{
switch (Application.platform)
{
case RuntimePlatform.OSXEditor:
case RuntimePlatform.OSXPlayer:
RunCommand($"open -a TrenchBroom '{mapFile}'");
break;
case RuntimePlatform.WindowsEditor:
case RuntimePlatform.WindowsPlayer:
// Sad. On Windows, TB is mostly portable so we may have to just show the file in Explorer, lol
if (TrenchBroomPath != null)
{
RunCommand($"\"{TrenchBroomPath}\" \"{mapFile}\"", useShell: false);
}
else
{
#if UNITY_EDITOR
EditorUtility.DisplayDialog(
title: "Where's TrenchBroom?",
message: "Tremble could not find TrenchBroom. Ensure TrenchBroom is installed and " +
"running, and Tremble will make a note of where it's installed for next time." +
" For now, I'll show your map in Explorer.", "Okay");
#endif
RunCommand($"explorer /select, \"{mapFile}\"");
}
break;
case RuntimePlatform.LinuxEditor:
case RuntimePlatform.LinuxPlayer:
RunCommand($"/usr/bin/trenchbroom '{mapFile}'");
break;
}
}
private static void RunCommand(string command, bool useShell = true)
{
command.Split(' ', out string exe, out string args);
#if UNITY_EDITOR_WIN || UNITY_STANDALONE_WIN
// Windows Command Prompt
if (useShell)
{
args = $"/c \"{exe} {args}\"";
exe = "cmd.exe";
}
#else
// Under macOS and Linux we assume /bin/sh is a thing
if (useShell)
{
args = $"-c \"{exe} {args}\"";
exe = "/bin/sh";
}
#endif
Process.Start(new ProcessStartInfo(exe, args) { WorkingDirectory = Directory.GetCurrentDirectory() });
}
}
}
@@ -0,0 +1,18 @@
fileFormatVersion: 2
guid: e7bf4a6e8c1404a21adc02ab38521201
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/TrenchBroomUtil.cs
uploadId: 939664
+365
View File
@@ -0,0 +1,365 @@
//
// This file is part of the Tremble package by Tiny Goose.
// Copyright (c) 2024-2026 TinyGoose Ltd., All Rights Reserved.
//
//#define USE_TYPEDB_IN_EDITOR
using System;
using System.Collections.Generic;
#if !UNITY_EDITOR || USE_TYPEDB_IN_EDITOR
using System.Linq;
using UnityEngine;
#endif
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace TinyGoose.Tremble
{
public static class TypeDatabase
{
private static Dictionary<string, Type> s_ClassLookup;
private static Dictionary<Type, object[]> s_ClassAttributes;
public static Type GetType(string fullName)
{
if (fullName == null)
return null;
#if UNITY_EDITOR && !USE_TYPEDB_IN_EDITOR
// Unsupported - but very fast!
return Unsupported.GetTypeFromFullName(fullName);
#else
if (s_ClassLookup == null)
{
GenerateClassLookup();
}
return s_ClassLookup.GetValueOrDefault(fullName);
#endif
}
public static IList<Type> GetTypesWithAttribute<TAttribute>()
where TAttribute : Attribute
{
#if UNITY_EDITOR && !USE_TYPEDB_IN_EDITOR
return TypeCache.GetTypesWithAttribute<TAttribute>();
#else
if (s_ClassLookup == null)
{
GenerateClassLookup();
}
List<Type> types = new(256);
foreach (KeyValuePair<Type, object[]> kvp in s_ClassAttributes)
{
foreach (object attribute in kvp.Value)
{
Type attributeType = attribute.GetType();
if (attributeType == typeof(TAttribute) || attributeType.IsSubclassOf(typeof(TAttribute)))
{
types.Add(kvp.Key);
}
}
}
return types;
#endif
}
#if !UNITY_EDITOR || USE_TYPEDB_IN_EDITOR
private static readonly HashSet<string> FORBIDDEN_ASSEMBLIES = new()
{
"mscorlib",
"UnityEngine",
"UnityEngine.AIModule",
"UnityEngine.AMDModule",
"UnityEngine.ARModule",
"UnityEngine.AccessibilityModule",
"UnityEngine.AndroidJNIModule",
"UnityEngine.AnimationModule",
"UnityEngine.AssetBundleModule",
"UnityEngine.AudioModule",
"UnityEngine.ClothModule",
"UnityEngine.ClusterInputModule",
"UnityEngine.ClusterRendererModule",
"UnityEngine.ContentLoadModule",
"UnityEngine.CoreModule",
"UnityEngine.CrashReportingModule",
"UnityEngine.DSPGraphModule",
"UnityEngine.DirectorModule",
"UnityEngine.GIModule",
"UnityEngine.GameCenterModule",
"UnityEngine.GraphicsStateCollectionSerializerModule",
"UnityEngine.GridModule",
"UnityEngine.HierarchyCoreModule",
"UnityEngine.HotReloadModule",
"UnityEngine.IMGUIModule",
"UnityEngine.ImageConversionModule",
"UnityEngine.InputModule",
"UnityEngine.InputForUIModule",
"UnityEngine.InputLegacyModule",
"UnityEngine.JSONSerializeModule",
"UnityEngine.LocalizationModule",
"UnityEngine.MarshallingModule",
"UnityEngine.MultiplayerModule",
"UnityEngine.NVIDIAModule",
"UnityEngine.ParticleSystemModule",
"UnityEngine.PerformanceReportingModule",
"UnityEngine.PhysicsModule",
"UnityEngine.Physics2DModule",
"UnityEngine.PropertiesModule",
"UnityEngine.RuntimeInitializeOnLoadManagerInitializerModule",
"UnityEngine.ScreenCaptureModule",
"UnityEngine.ShaderVariantAnalyticsModule",
"UnityEngine.SharedInternalsModule",
"UnityEngine.SpriteMaskModule",
"UnityEngine.SpriteShapeModule",
"UnityEngine.StreamingModule",
"UnityEngine.SubstanceModule",
"UnityEngine.SubsystemsModule",
"UnityEngine.TLSModule",
"UnityEngine.TerrainModule",
"UnityEngine.TerrainPhysicsModule",
"UnityEngine.TextCoreFontEngineModule",
"UnityEngine.TextCoreTextEngineModule",
"UnityEngine.TextRenderingModule",
"UnityEngine.TilemapModule",
"UnityEngine.UIModule",
"UnityEngine.UIElementsModule",
"UnityEngine.UmbraModule",
"UnityEngine.UnityAnalyticsModule",
"UnityEngine.UnityAnalyticsCommonModule",
"UnityEngine.UnityConnectModule",
"UnityEngine.UnityCurlModule",
"UnityEngine.UnityTestProtocolModule",
"UnityEngine.UnityWebRequestModule",
"UnityEngine.UnityWebRequestAssetBundleModule",
"UnityEngine.UnityWebRequestAudioModule",
"UnityEngine.UnityWebRequestTextureModule",
"UnityEngine.UnityWebRequestWWWModule",
"UnityEngine.VFXModule",
"UnityEngine.VRModule",
"UnityEngine.VehiclesModule",
"UnityEngine.VideoModule",
"UnityEngine.VirtualTexturingModule",
"UnityEngine.WindModule",
"UnityEngine.XRModule",
"UnityEditor",
"UnityEditor.AccessibilityModule",
"UnityEditor.AdaptivePerformanceModule",
"UnityEditor.BuildProfileModule",
"UnityEditor.CoreBusinessMetricsModule",
"UnityEditor.CoreModule",
"UnityEditor.DeviceSimulatorModule",
"UnityEditor.DiagnosticsModule",
"UnityEditor.EditorToolbarModule",
"UnityEditor.GIModule",
"UnityEditor.GraphViewModule",
"UnityEditor.GraphicsStateCollectionSerializerModule",
"UnityEditor.GridAndSnapModule",
"UnityEditor.GridModule",
"UnityEditor.MultiplayerModule",
"UnityEditor.Physics2DModule",
"UnityEditor.PhysicsModule",
"UnityEditor.PresetsUIModule",
"UnityEditor.PropertiesModule",
"UnityEditor.QuickSearchModule",
"UnityEditor.SafeModeModule",
"UnityEditor.SceneTemplateModule",
"UnityEditor.SceneViewModule",
"UnityEditor.ShaderFoundryModule",
"UnityEditor.SketchUpModule",
"UnityEditor.SpriteMaskModule",
"UnityEditor.SpriteShapeModule",
"UnityEditor.SubstanceModule",
"UnityEditor.TerrainModule",
"UnityEditor.TextCoreFontEngineModule",
"UnityEditor.TextCoreTextEngineModule",
"UnityEditor.TextRenderingModule",
"UnityEditor.TilemapModule",
"UnityEditor.TreeModule",
"UnityEditor.UIAutomationModule",
"UnityEditor.UIBuilderModule",
"UnityEditor.UIElementsModule",
"UnityEditor.UIElementsSamplesModule",
"UnityEditor.UmbraModule",
"UnityEditor.UnityConnectModule",
"UnityEditor.VFXModule",
"UnityEditor.VideoModule",
"UnityEditor.XRModule",
"netstandard",
"System.Core",
"System",
"Mono.Security",
"System.Configuration",
"System.Xml",
"System.Numerics",
"System.Data",
"System.Transactions",
"System.EnterpriseServices",
"System.Runtime.Serialization",
"System.ServiceModel.Internals",
"System.Xml.Linq",
"Unity.Cecil",
"Bee.BeeDriver2",
"Bee.BinLog",
"UnityEditor.Graphs",
"UnityEditor.OSXStandalone.Extensions",
"UnityEditor.iOS.Extensions.Xcode",
"Autodesk.Fbx.BuildTestAssets",
"Autodesk.Fbx",
"Autodesk.Fbx.Editor",
"PPv2URPConverters",
"Unity.Addressables",
"Unity.Addressables.Editor",
"Unity.AI.Navigation",
"Unity.AI.Navigation.Editor.ConversionSystem",
"Unity.AI.Navigation.Editor",
"Unity.AI.Navigation.Updater",
"Unity.Bindings.OpenImageIO.Editor",
"Unity.Burst.CodeGen",
"Unity.Burst",
"Unity.Burst.Editor",
"Unity.Cinemachine",
"Unity.Cinemachine.Editor",
"Unity.CollabProxy.Editor",
"Unity.Collections.CodeGen",
"Unity.Collections",
"Unity.Collections.Editor",
"Unity.EditorCoroutines.Editor",
"Unity.Formats.Fbx.Editor",
"Unity.Formats.Fbx.Runtime",
"Unity.InputSystem",
"Unity.InputSystem.ForUI",
"Unity.InputSystem.TestFramework",
"Unity.Mathematics",
"Unity.Mathematics.Editor",
"Unity.MemoryProfiler",
"Unity.MemoryProfiler.Editor",
"Unity.MemoryProfiler.Editor.MemoryProfilerModule",
"Unity.PerformanceTesting",
"Unity.PerformanceTesting.Editor",
"Unity.PlasticSCM.Editor",
"Unity.Profiling.Core",
"Unity.Recorder.Base",
"Unity.Recorder",
"Unity.Recorder.Editor",
"Unity.Rendering.LightTransport.Editor",
"Unity.Rendering.LightTransport.Runtime",
"Unity.RenderPipeline.Universal.ShaderLibrary",
"Unity.RenderPipelines.Core.Editor",
"Unity.RenderPipelines.Core.Editor.Shared",
"Unity.RenderPipelines.Core.Runtime",
"Unity.RenderPipelines.Core.Runtime.Shared",
"Unity.RenderPipelines.Core.ShaderLibrary",
"Unity.RenderPipelines.GPUDriven.Runtime",
"Unity.RenderPipelines.ShaderGraph.ShaderGraphLibrary",
"Unity.RenderPipelines.Universal.2D.Runtime",
"Unity.RenderPipelines.Universal.Config.Runtime",
"Unity.RenderPipelines.Universal.Editor",
"Unity.RenderPipelines.Universal.Runtime",
"Unity.RenderPipelines.Universal.Shaders",
"Unity.ResourceManager",
"Unity.Rider.Editor",
"Unity.ScriptableBuildPipeline",
"Unity.ScriptableBuildPipeline.Editor",
"Unity.Searcher.Editor",
"Unity.Serialization",
"Unity.Serialization.Editor",
"Unity.Services.Authentication",
"Unity.Services.Authentication.Editor",
"Unity.Services.Authentication.Editor.Shared",
"Unity.Services.Authentication.PlayerAccounts",
"Unity.Services.Authentication.PlayerAccounts.Editor",
"Unity.Services.Core.Analytics",
"Unity.Services.Core.Configuration",
"Unity.Services.Core.Configuration.Editor",
"Unity.Services.Core.Device",
"Unity.Services.Core",
"Unity.Services.Core.Editor",
"Unity.Services.Core.Environments",
"Unity.Services.Core.Environments.Editor",
"Unity.Services.Core.Environments.Internal",
"Unity.Services.Core.Internal",
"Unity.Services.Core.Networking",
"Unity.Services.Core.Registration",
"Unity.Services.Core.Scheduler",
"Unity.Services.Core.Telemetry",
"Unity.Services.Core.Threading",
"Unity.Services.Ugc",
"Unity.Services.Ugc.DocCodeSamples",
"Unity.Services.Ugc.Internal.Generated",
"Unity.Settings.Editor",
"Unity.ShaderGraph.Editor",
"Unity.ShaderGraph.Utilities",
"Unity.Splines",
"Unity.Splines.Editor",
"Unity.TextMeshPro",
"Unity.TextMeshPro.Editor",
"Unity.Timeline",
"Unity.Timeline.Editor",
"UnityEditor.TestRunner",
"UnityEditor.UI",
"UnityEngine.TestRunner",
"UnityEngine.UI",
"Mono.Cecil.Pdb",
"SingularityGroup.HotReload.RuntimeDependencies",
"Unity.Burst.Cecil.Pdb",
"Unity.Collections.LowLevel.ILSupport",
"nunit.framework",
"SingularityGroup.HotReload.EditorDependencies",
"unityplastic",
"Unity.Plastic.Antlr3.Runtime",
"Unity.Burst.Cecil.Mdb",
"Unity.Burst.Cecil",
"SingularityGroup.HotReload.RuntimeDependencies2020",
"DOTweenEditor",
"Mono.Cecil.Mdb",
"Unity.Plastic.Newtonsoft.Json",
"Unity.Burst.Unsafe",
"Mono.Cecil.Rocks",
"Mono.Cecil",
"Unity.Burst.Cecil.Rocks",
"System.Net.Http",
"Unity.CompilationPipeline.Common",
"System.Drawing",
"System.Runtime",
"Mono.Posix",
"System.Web",
"Microsoft.GeneratedCode",
"JetBrains.Rider.Unity.Editor.Plugin.Net46.Repacked",
"ExCSS.Unity",
"Microsoft.GeneratedCode",
};
public static void GenerateClassLookup()
{
s_ClassLookup = new(1024);
s_ClassAttributes = new(1024);
IEnumerable<Type> types = AppDomain.CurrentDomain
.GetAssemblies()
.Where(ass => !FORBIDDEN_ASSEMBLIES.Contains(ass.GetName().Name))
.SelectMany(ass => ass.GetTypes())
.Where(t => t.FullName != null);
foreach (Type availableType in types)
{
s_ClassLookup[availableType.FullName] = availableType;
if (availableType.IsSubclassOf(typeof(MonoBehaviour)) || availableType.IsSubclassOf(typeof(TrembleFieldConverter)))
{
s_ClassAttributes.Add(availableType, availableType.GetCustomAttributes(true));
}
}
}
#endif
}
}
+10
View File
@@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5e121f8ff98d4f5f98898fd968b7d827
timeCreated: 1715667413
AssetOrigin:
serializedVersion: 1
productId: 277805
packageName: Tremble - Map Importer
packageVersion: 1.11.1
assetPath: Assets/Tremble/Utils/TypeDatabase.cs
uploadId: 939664