@@ -1,35 +1,46 @@ | |||
fileFormatVersion: 2 | |||
guid: e274dd71b041d6e408c83336cc82256b | |||
timeCreated: 1490201715 | |||
licenseType: Store | |||
ModelImporter: | |||
serializedVersion: 19 | |||
serializedVersion: 23 | |||
fileIDToRecycleName: | |||
100000: //RootNode | |||
400000: //RootNode | |||
2300000: //RootNode | |||
3300000: //RootNode | |||
4300000: "\u041A\u0443\u0431.003" | |||
2186277476908879412: ImportLogs | |||
externalObjects: | |||
- first: | |||
type: UnityEngine:Material | |||
assembly: UnityEngine.CoreModule | |||
name: No Name | |||
second: {fileID: 2100000, guid: b3faac127ca512e418f5512a49b9e0f1, type: 2} | |||
materials: | |||
importMaterials: 1 | |||
materialName: 0 | |||
materialSearch: 1 | |||
materialLocation: 0 | |||
animations: | |||
legacyGenerateAnimations: 4 | |||
bakeSimulation: 0 | |||
resampleCurves: 1 | |||
optimizeGameObjects: 0 | |||
motionNodeName: | |||
rigImportErrors: | |||
rigImportWarnings: | |||
animationImportErrors: | |||
animationImportWarnings: | |||
animationRetargetingWarnings: | |||
animationDoRetargetingWarnings: 0 | |||
importAnimatedCustomProperties: 0 | |||
importConstraints: 0 | |||
animationCompression: 1 | |||
animationRotationError: 0.5 | |||
animationPositionError: 0.5 | |||
animationScaleError: 0.5 | |||
animationWrapMode: 0 | |||
extraExposedTransformPaths: [] | |||
extraUserProperties: [] | |||
clipAnimations: [] | |||
isReadable: 1 | |||
meshes: | |||
@@ -37,25 +48,38 @@ ModelImporter: | |||
globalScale: 1 | |||
meshCompression: 0 | |||
addColliders: 0 | |||
useSRGBMaterialColor: 1 | |||
importVisibility: 0 | |||
importBlendShapes: 1 | |||
importCameras: 0 | |||
importLights: 0 | |||
swapUVChannels: 0 | |||
generateSecondaryUV: 0 | |||
useFileUnits: 1 | |||
optimizeMeshForGPU: 1 | |||
keepQuads: 0 | |||
weldVertices: 1 | |||
preserveHierarchy: 0 | |||
indexFormat: 1 | |||
secondaryUVAngleDistortion: 8 | |||
secondaryUVAreaDistortion: 15.000001 | |||
secondaryUVHardAngle: 88 | |||
secondaryUVPackMargin: 4 | |||
useFileScale: 1 | |||
previousCalculatedGlobalScale: 1 | |||
hasPreviousCalculatedGlobalScale: 0 | |||
tangentSpace: | |||
normalSmoothAngle: 60 | |||
normalImportMode: 0 | |||
tangentImportMode: 3 | |||
normalCalculationMode: 0 | |||
legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 1 | |||
blendShapeNormalImportMode: 1 | |||
normalSmoothingSource: 0 | |||
importAnimation: 1 | |||
copyAvatar: 0 | |||
humanDescription: | |||
serializedVersion: 2 | |||
human: [] | |||
skeleton: [] | |||
armTwist: 0.5 | |||
@@ -67,6 +91,8 @@ ModelImporter: | |||
feetSpacing: 0 | |||
rootMotionBoneName: | |||
hasTranslationDoF: 0 | |||
hasExtraRoot: 0 | |||
skeletonHasParents: 0 | |||
lastHumanDescriptionAvatarSource: {instanceID: 0} | |||
animationType: 0 | |||
humanoidOversampling: 1 |
@@ -1,11 +0,0 @@ | |||
fileFormatVersion: 2 | |||
guid: f1c08f05ea962fc428f6ce7e953cb1c4 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,11 +0,0 @@ | |||
fileFormatVersion: 2 | |||
guid: 019f01580ed7a3346ac2d164ecbd612e | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,8 +0,0 @@ | |||
fileFormatVersion: 2 | |||
guid: 59534c1913c420f428eb1266b19ace68 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,9 +0,0 @@ | |||
fileFormatVersion: 2 | |||
guid: 9ceea87507dde1e4e9f4d2f9b3218898 | |||
folderAsset: yes | |||
timeCreated: 1521506758 | |||
licenseType: Pro | |||
DefaultImporter: | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -1,9 +0,0 @@ | |||
fileFormatVersion: 2 | |||
guid: c3215fde7fc71504fb3b192b10dbf263 | |||
folderAsset: yes | |||
timeCreated: 1521506802 | |||
licenseType: Pro | |||
DefaultImporter: | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: ffdcfd8007995e041b2708d436649929 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 2ecfdb4bf9f28dc4cb39b3000582c3a5 | |||
NativeFormatImporter: | |||
externalObjects: {} | |||
mainObjectFileID: 11400000 | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 6a4d431d0263ec14e809f337be489069 | |||
NativeFormatImporter: | |||
externalObjects: {} | |||
mainObjectFileID: 11400000 | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 53b01afe807b1db44932e99813167910 | |||
NativeFormatImporter: | |||
externalObjects: {} | |||
mainObjectFileID: 11400000 | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 49962594cb127494dbaf43127eb2bfc7 | |||
NativeFormatImporter: | |||
externalObjects: {} | |||
mainObjectFileID: 11400000 | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 71095558784d7d34a9a1e2d0b8d037f8 | |||
NativeFormatImporter: | |||
externalObjects: {} | |||
mainObjectFileID: 11400000 | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 8dbd563ee170a8642905b582854e2000 | |||
NativeFormatImporter: | |||
externalObjects: {} | |||
mainObjectFileID: 11400000 | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: adae4074d0c3d514c954cbfeb6571ec8 | |||
NativeFormatImporter: | |||
externalObjects: {} | |||
mainObjectFileID: 11400000 | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 80e2b5b22255160459ff53c58886ef2f | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: a92baf355836cf6469600cd8fcff68e5 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 1463dd1d6b2a0174e8c64419066dae25 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,102 @@ | |||
fileFormatVersion: 2 | |||
guid: 8b6535096cfa29340897276abbdd015f | |||
PluginImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
iconMap: {} | |||
executionOrder: {} | |||
defineConstraints: [] | |||
isPreloaded: 0 | |||
isOverridable: 0 | |||
isExplicitlyReferenced: 0 | |||
validateReferences: 1 | |||
platformData: | |||
- first: | |||
'': Any | |||
second: | |||
enabled: 0 | |||
settings: | |||
Exclude Android: 1 | |||
Exclude Editor: 0 | |||
Exclude Linux: 1 | |||
Exclude Linux64: 1 | |||
Exclude LinuxUniversal: 1 | |||
Exclude OSXUniversal: 1 | |||
Exclude Win: 1 | |||
Exclude Win64: 1 | |||
- first: | |||
Android: Android | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: ARMv7 | |||
- first: | |||
Any: | |||
second: | |||
enabled: 0 | |||
settings: {} | |||
- first: | |||
Editor: Editor | |||
second: | |||
enabled: 1 | |||
settings: | |||
CPU: AnyCPU | |||
DefaultValueInitialized: true | |||
OS: AnyOS | |||
- first: | |||
Facebook: Win | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: AnyCPU | |||
- first: | |||
Facebook: Win64 | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: AnyCPU | |||
- first: | |||
Standalone: Linux | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: x86 | |||
- first: | |||
Standalone: Linux64 | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: x86_64 | |||
- first: | |||
Standalone: LinuxUniversal | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: None | |||
- first: | |||
Standalone: OSXUniversal | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: AnyCPU | |||
- first: | |||
Standalone: Win | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: AnyCPU | |||
- first: | |||
Standalone: Win64 | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: AnyCPU | |||
- first: | |||
Windows Store Apps: WindowsStoreApps | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: AnyCPU | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,7 @@ | |||
fileFormatVersion: 2 | |||
guid: 4eab08543006f4f4bb4b3dd5a446234b | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,46 @@ | |||
fileFormatVersion: 2 | |||
guid: 585dd63e377866248b16bdba915820ed | |||
PluginImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
iconMap: {} | |||
executionOrder: {} | |||
defineConstraints: [] | |||
isPreloaded: 0 | |||
isOverridable: 0 | |||
isExplicitlyReferenced: 0 | |||
validateReferences: 1 | |||
platformData: | |||
- first: | |||
'': Any | |||
second: | |||
enabled: 0 | |||
settings: | |||
Exclude Android: 1 | |||
Exclude Editor: 0 | |||
Exclude Linux: 1 | |||
Exclude Linux64: 1 | |||
Exclude LinuxUniversal: 1 | |||
Exclude OSXUniversal: 1 | |||
Exclude Win: 1 | |||
Exclude Win64: 1 | |||
- first: | |||
Any: | |||
second: | |||
enabled: 0 | |||
settings: {} | |||
- first: | |||
Editor: Editor | |||
second: | |||
enabled: 1 | |||
settings: | |||
DefaultValueInitialized: true | |||
- first: | |||
Windows Store Apps: WindowsStoreApps | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: AnyCPU | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,7 @@ | |||
fileFormatVersion: 2 | |||
guid: e9390d97fcdceaf41935918dbb4367ae | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,46 @@ | |||
fileFormatVersion: 2 | |||
guid: b335798a4f28bec40ba9b3d4a15acee7 | |||
PluginImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
iconMap: {} | |||
executionOrder: {} | |||
defineConstraints: [] | |||
isPreloaded: 0 | |||
isOverridable: 0 | |||
isExplicitlyReferenced: 0 | |||
validateReferences: 1 | |||
platformData: | |||
- first: | |||
'': Any | |||
second: | |||
enabled: 0 | |||
settings: | |||
Exclude Android: 1 | |||
Exclude Editor: 0 | |||
Exclude Linux: 1 | |||
Exclude Linux64: 1 | |||
Exclude LinuxUniversal: 1 | |||
Exclude OSXUniversal: 1 | |||
Exclude Win: 1 | |||
Exclude Win64: 1 | |||
- first: | |||
Any: | |||
second: | |||
enabled: 0 | |||
settings: {} | |||
- first: | |||
Editor: Editor | |||
second: | |||
enabled: 1 | |||
settings: | |||
DefaultValueInitialized: true | |||
- first: | |||
Windows Store Apps: WindowsStoreApps | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: AnyCPU | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,7 @@ | |||
fileFormatVersion: 2 | |||
guid: 3871dc7fc7ece2e49a31ab04c66dec7f | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,46 @@ | |||
fileFormatVersion: 2 | |||
guid: 21dcba1a47cc8c84381629950b692129 | |||
PluginImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
iconMap: {} | |||
executionOrder: {} | |||
defineConstraints: [] | |||
isPreloaded: 0 | |||
isOverridable: 0 | |||
isExplicitlyReferenced: 0 | |||
validateReferences: 1 | |||
platformData: | |||
- first: | |||
'': Any | |||
second: | |||
enabled: 0 | |||
settings: | |||
Exclude Android: 1 | |||
Exclude Editor: 0 | |||
Exclude Linux: 1 | |||
Exclude Linux64: 1 | |||
Exclude LinuxUniversal: 1 | |||
Exclude OSXUniversal: 1 | |||
Exclude Win: 1 | |||
Exclude Win64: 1 | |||
- first: | |||
Any: | |||
second: | |||
enabled: 0 | |||
settings: {} | |||
- first: | |||
Editor: Editor | |||
second: | |||
enabled: 1 | |||
settings: | |||
DefaultValueInitialized: true | |||
- first: | |||
Windows Store Apps: WindowsStoreApps | |||
second: | |||
enabled: 0 | |||
settings: | |||
CPU: AnyCPU | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,7 @@ | |||
fileFormatVersion: 2 | |||
guid: 7da37ac58a466c549b2bb11c9c788211 | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: c33dc74e68b646947b8b6d4adfa03367 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 63db918e9328d1a4ab6dd0df23a759cd | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,18 @@ | |||
using System; | |||
namespace VRC.Udon.Editor.ProgramSources.Attributes | |||
{ | |||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] | |||
public class UdonProgramSourceNewMenuAttribute : Attribute | |||
{ | |||
public Type Type { get; } | |||
public string DisplayName { get; } | |||
public UdonProgramSourceNewMenuAttribute(Type type, string displayName) | |||
{ | |||
Type = type; | |||
DisplayName = displayName; | |||
} | |||
} | |||
} | |||
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 0e5ced9511d591140b191bbd9e948e61 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,57 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Diagnostics; | |||
using UnityEditor; | |||
using VRC.Udon.Common.Interfaces; | |||
using VRC.Udon.ProgramSources; | |||
using VRC.Udon.Serialization.OdinSerializer; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
[CustomEditor(typeof(SerializedUdonProgramAsset))] | |||
public class SerializedUdonProgramAssetEditor : UnityEditor.Editor | |||
{ | |||
private SerializedProperty _serializedProgramBytesStringSerializedProperty; | |||
private SerializedProperty _serializationDataFormatSerializedProperty; | |||
private void OnEnable() | |||
{ | |||
_serializedProgramBytesStringSerializedProperty = serializedObject.FindProperty("serializedProgramBytesString"); | |||
_serializationDataFormatSerializedProperty = serializedObject.FindProperty("serializationDataFormat"); | |||
} | |||
public override void OnInspectorGUI() | |||
{ | |||
DrawSerializationDebug(); | |||
} | |||
[Conditional("UDON_DEBUG")] | |||
private void DrawSerializationDebug() | |||
{ | |||
EditorGUILayout.LabelField($"DataFormat: {(DataFormat)_serializationDataFormatSerializedProperty.enumValueIndex}"); | |||
if(string.IsNullOrEmpty(_serializedProgramBytesStringSerializedProperty.stringValue)) | |||
{ | |||
return; | |||
} | |||
if(_serializationDataFormatSerializedProperty.enumValueIndex == (int)DataFormat.JSON) | |||
{ | |||
using(new EditorGUI.DisabledScope(true)) | |||
{ | |||
EditorGUILayout.TextArea(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(_serializedProgramBytesStringSerializedProperty.stringValue))); | |||
} | |||
} | |||
else | |||
{ | |||
using(new EditorGUI.DisabledScope(true)) | |||
{ | |||
SerializedUdonProgramAsset serializedUdonProgramAsset = (SerializedUdonProgramAsset)target; | |||
IUdonProgram udonProgram = serializedUdonProgramAsset.RetrieveProgram(); | |||
byte[] serializedBytes = SerializationUtility.SerializeValue(udonProgram, DataFormat.JSON, out List<UnityEngine.Object> _); | |||
EditorGUILayout.TextArea(System.Text.Encoding.UTF8.GetString(serializedBytes)); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: e1b5b45f24b268b42826fc5c5497dc15 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 97384715210e4454eb09884be2eaa216 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,90 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using JetBrains.Annotations; | |||
using UnityEditor; | |||
using UnityEngine; | |||
using VRC.Udon.Editor; | |||
using VRC.Udon.Editor.ProgramSources; | |||
using VRC.Udon.Editor.ProgramSources.Attributes; | |||
[assembly: UdonProgramSourceNewMenu(typeof(UdonAssemblyProgramAsset), "Udon Assembly Program Asset")] | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
[CreateAssetMenu(menuName = "VRChat/Udon/Udon Assembly Program Asset", fileName = "New Udon Assembly Program Asset")] | |||
public class UdonAssemblyProgramAsset : UdonProgramAsset | |||
{ | |||
[SerializeField] | |||
protected string udonAssembly = ""; | |||
[SerializeField] | |||
private string assemblyError = null; | |||
protected override void DrawProgramSourceGUI(UdonBehaviour udonBehaviour, ref bool dirty) | |||
{ | |||
DrawAssemblyTextArea(!Application.isPlaying, ref dirty); | |||
DrawAssemblyErrorTextArea(); | |||
base.DrawProgramSourceGUI(udonBehaviour, ref dirty); | |||
} | |||
protected override void RefreshProgramImpl() | |||
{ | |||
AssembleProgram(); | |||
} | |||
[PublicAPI] | |||
protected virtual void DrawAssemblyTextArea(bool allowEditing, ref bool dirty) | |||
{ | |||
EditorGUILayout.LabelField("Assembly Code", EditorStyles.boldLabel); | |||
if(GUILayout.Button("Copy Assembly To Clipboard")) | |||
{ | |||
EditorGUIUtility.systemCopyBuffer = udonAssembly; | |||
} | |||
EditorGUI.BeginChangeCheck(); | |||
using(new EditorGUI.DisabledScope(!allowEditing)) | |||
{ | |||
string newAssembly = EditorGUILayout.TextArea(udonAssembly); | |||
if(EditorGUI.EndChangeCheck()) | |||
{ | |||
dirty = true; | |||
Undo.RecordObject(this, "Edit Assembly Program Code"); | |||
udonAssembly = newAssembly; | |||
UdonEditorManager.Instance.QueueProgramSourceRefresh(this); | |||
} | |||
} | |||
} | |||
[PublicAPI] | |||
protected void DrawAssemblyErrorTextArea() | |||
{ | |||
if(string.IsNullOrEmpty(assemblyError)) | |||
{ | |||
return; | |||
} | |||
EditorGUILayout.LabelField("Assembly Error", EditorStyles.boldLabel); | |||
using(new EditorGUI.DisabledScope(true)) | |||
{ | |||
EditorGUILayout.TextArea(assemblyError); | |||
} | |||
} | |||
[PublicAPI] | |||
protected void AssembleProgram() | |||
{ | |||
try | |||
{ | |||
program = UdonEditorManager.Instance.Assemble(udonAssembly); | |||
assemblyError = null; | |||
} | |||
catch(Exception e) | |||
{ | |||
program = null; | |||
assemblyError = e.Message; | |||
Debug.LogException(e); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 22203902d63dec94194fefc3e155c43b | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,9 @@ | |||
using UnityEditor; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
[CustomEditor(typeof(UdonAssemblyProgramAsset))] | |||
public class UdonAssemblyProgramAssetEditor : UdonProgramAssetEditor | |||
{ | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 3df823f3ab561fc43bcb81286e14b91d | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,27 @@ | |||
using System.IO; | |||
using JetBrains.Annotations; | |||
using UnityEditor; | |||
using UnityEditor.Experimental.AssetImporters; | |||
using UnityEngine; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
[ScriptedImporter(1, "uasm")] | |||
[UsedImplicitly] | |||
public class UdonAssemblyProgramAssetImporter : ScriptedImporter | |||
{ | |||
public override void OnImportAsset(AssetImportContext ctx) | |||
{ | |||
UdonAssemblyProgramAsset udonAssemblyProgramAsset = ScriptableObject.CreateInstance<UdonAssemblyProgramAsset>(); | |||
SerializedObject serializedUdonAssemblyProgramAsset = new SerializedObject(udonAssemblyProgramAsset); | |||
SerializedProperty udonAssemblyProperty = serializedUdonAssemblyProgramAsset.FindProperty("udonAssembly"); | |||
udonAssemblyProperty.stringValue = File.ReadAllText(ctx.assetPath); | |||
serializedUdonAssemblyProgramAsset.ApplyModifiedProperties(); | |||
udonAssemblyProgramAsset.RefreshProgram(); | |||
ctx.AddObjectToAsset("Imported Udon Assembly Program", udonAssemblyProgramAsset); | |||
ctx.SetMainObject(udonAssemblyProgramAsset); | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 3c0638314c289c24193b47d1c53c9fca | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 45d10a08cb4b5784b9f6afed3cce0f07 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,35 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using UnityEngine; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
internal class SearchComparer : IComparer<string> | |||
{ | |||
public SearchComparer(string searchString) | |||
{ | |||
_searchString = searchString; | |||
} | |||
private readonly string _searchString; | |||
public int Compare(string x, string y) | |||
{ | |||
if (x == null || y == null) | |||
{ | |||
return 0; | |||
} | |||
//-1 is they're out of order, 0 is order doesn't matter, 1 is they're in order | |||
x = x.ReplaceFirst("const ", ""); | |||
y = y.ReplaceFirst("const ", ""); | |||
int xIndex = x.IndexOf(_searchString, StringComparison.InvariantCultureIgnoreCase); | |||
int yIndex = y.IndexOf(_searchString, StringComparison.InvariantCultureIgnoreCase); | |||
int compareIndex = xIndex.CompareTo(yIndex); | |||
if (compareIndex != 0) return compareIndex; | |||
string xDiff = x.ReplaceFirst(_searchString, ""); | |||
string yDiff = y.ReplaceFirst(_searchString, ""); | |||
return string.Compare(xDiff, yDiff, StringComparison.InvariantCultureIgnoreCase); | |||
} | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
fileFormatVersion: 2 | |||
guid: d12298313646456a8070307238fedf48 | |||
timeCreated: 1575758808 |
@@ -0,0 +1,483 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using UnityEngine; | |||
using UnityEditor; | |||
using UnityEditor.Graphs; | |||
// ReSharper disable MemberCanBePrivate.Global | |||
// ReSharper disable InvertIf | |||
// ReSharper disable EnforceIfStatementBraces | |||
// ReSharper disable UnusedMember.Local | |||
// ReSharper disable Unity.InefficientMultiplicationOrder | |||
// ReSharper disable PossibleNullReferenceException | |||
// ReSharper disable RedundantExplicitArrayCreation | |||
// ReSharper disable RedundantExplicitParamsArrayCreation | |||
// ReSharper disable FieldCanBeMadeReadOnly.Global | |||
// ReSharper disable ConvertToConstant.Global | |||
// ReSharper disable RedundantNameQualifier | |||
// ReSharper disable ArrangeThisQualifier | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
public class UdonEdgeGUI : IEdgeGUI | |||
{ | |||
public EdgeGUI.EdgeStyle edgeStyle = EdgeGUI.EdgeStyle.Curvy; | |||
private static Slot _sDragSourceSlot; | |||
private static Slot _sDropTarget; | |||
public List<int> edgeSelection { get; set; } | |||
public GraphGUI host { get; set; } | |||
private Edge DontDrawEdge { get; set; } | |||
private Edge MoveEdge { get; set; } | |||
public UdonEdgeGUI() | |||
{ | |||
edgeSelection = new List<int>(); | |||
} | |||
public void BeginSlotDragging(Slot slot, bool allowStartDrag, bool allowEndDrag) | |||
{ | |||
if(allowStartDrag) | |||
{ | |||
_sDragSourceSlot = slot; | |||
Event.current.Use(); | |||
} | |||
if(allowEndDrag && slot.edges.Count > 0) | |||
{ | |||
MoveEdge = slot.edges[slot.edges.Count - 1]; | |||
_sDragSourceSlot = MoveEdge.fromSlot; | |||
_sDropTarget = slot; | |||
Event.current.Use(); | |||
} | |||
} | |||
public void DoDraggedEdge() | |||
{ | |||
if(_sDragSourceSlot != null) | |||
{ | |||
EventType typeForControl = Event.current.GetTypeForControl(0); | |||
if(typeForControl != EventType.Repaint) | |||
{ | |||
if(typeForControl == EventType.MouseDrag) | |||
{ | |||
_sDropTarget = null; | |||
DontDrawEdge = null; | |||
Event.current.Use(); | |||
} | |||
} | |||
else | |||
{ | |||
Assembly unityEngineAssembly = Assembly.GetAssembly(typeof(UnityEngine.GUI)); | |||
Type guiClipType = unityEngineAssembly.GetType("UnityEngine.GUIClip", true); | |||
FieldInfo propInfo = typeof(Slot).GetField( | |||
"m_Position", | |||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); | |||
if(propInfo == null) Debug.LogError("PropInfo m_Position is null!"); | |||
Rect position = (Rect)propInfo.GetValue(_sDragSourceSlot); | |||
Vector2 end = Event.current.mousePosition; | |||
if(_sDropTarget != null) | |||
{ | |||
Rect position2 = (Rect)propInfo.GetValue(_sDropTarget); | |||
object[] endArgs = {new Vector2(position2.x, position2.y + 9f)}; | |||
ParameterModifier endP = new ParameterModifier(1); | |||
endP[0] = true; | |||
ParameterModifier[] endMods = {endP}; | |||
MethodInfo endClipRect = guiClipType.GetMethod( | |||
"Clip", | |||
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | | |||
BindingFlags.FlattenHierarchy, | |||
Type.DefaultBinder, | |||
new Type[] {typeof(Vector2)}, | |||
endMods); | |||
end = (Vector2)endClipRect.Invoke(null, endArgs); | |||
} | |||
object[] startArgs = {new Vector2(position.xMax, position.y + 9f)}; | |||
ParameterModifier startP = new ParameterModifier(1); | |||
startP[0] = true; | |||
ParameterModifier[] startMods = {startP}; | |||
MethodInfo startClipRect = guiClipType.GetMethod( | |||
"Clip", | |||
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | | |||
BindingFlags.FlattenHierarchy, | |||
Type.DefaultBinder, | |||
new Type[] {typeof(Vector2)}, | |||
startMods); | |||
Vector2 start = (Vector2)startClipRect.Invoke(null, startArgs); | |||
Color edgeColor = Color.white; | |||
if (_sDragSourceSlot.dataType != null) | |||
edgeColor = UdonGraphGUI.MapTypeToColor(_sDragSourceSlot.dataType); | |||
DrawEdge(start, end, (Texture2D)Styles.selectedConnectionTexture.image, edgeColor, edgeStyle); | |||
} | |||
} | |||
} | |||
public void DoEdges() | |||
{ | |||
int num = 0; | |||
int num2 = 0; | |||
if(Event.current.type == EventType.Repaint) | |||
{ | |||
foreach(Edge current in host.graph.edges) | |||
{ | |||
if(current == DontDrawEdge || current == MoveEdge) continue; | |||
Texture2D tex = (Texture2D)Styles.connectionTexture.image; | |||
if(num < edgeSelection.Count && edgeSelection[num] == num2) | |||
{ | |||
num++; | |||
tex = (Texture2D)Styles.selectedConnectionTexture.image; | |||
} | |||
// TODO: CLEAN-UP | |||
Color color; | |||
if(!current.toSlot.isFlowSlot && current.toSlot.dataType != null && | |||
current.toSlot.dataType.IsSubclassOf(typeof(UnityEngine.Object))) | |||
{ | |||
color = EdgeGUI.kObjectTypeEdgeColor; | |||
} | |||
Color niceGrey = new Color(.85f, .85f, .85f); | |||
color = current.fromSlot.dataType != null ? UdonGraphGUI.MapTypeToColor(current.fromSlot.dataType) : niceGrey; | |||
Color endColor = new Color(.85f, .85f, .85f); | |||
if (current.toSlot.dataType != null) | |||
{ | |||
endColor = UdonGraphGUI.MapTypeToColor(current.toSlot.dataType); | |||
} | |||
DrawEdge(current, tex, color != endColor ? Color.Lerp(color, endColor, .5f) : color, edgeStyle); | |||
num2++; | |||
} | |||
} | |||
if(_sDragSourceSlot == null) return; | |||
if(Event.current.type != EventType.MouseUp) return; | |||
if(MoveEdge != null) | |||
{ | |||
host.graph.RemoveEdge(MoveEdge); | |||
MoveEdge = null; | |||
} | |||
if(_sDropTarget != null) return; | |||
EndDragging(); | |||
Event.current.Use(); | |||
} | |||
private static void DrawEdge(Edge e, Texture2D tex, Color color, EdgeGUI.EdgeStyle style) | |||
{ | |||
Vector2 start; | |||
Vector2 end; | |||
GetEdgeEndPoints(e, out start, out end); | |||
DrawEdge(start, end, tex, color, style); | |||
} | |||
private static void DrawEdge(Edge e, Texture2D tex, Color color, Color endColor, EdgeGUI.EdgeStyle style) | |||
{ | |||
Vector2 start; | |||
Vector2 end; | |||
GetEdgeEndPoints(e, out start, out end); | |||
DrawEdge(start, end, tex, color, endColor, style); | |||
} | |||
private static void DrawEdge(Vector2 start, Vector2 end, Texture2D tex, Color color, EdgeGUI.EdgeStyle style) | |||
{ | |||
if(style != EdgeGUI.EdgeStyle.Angular) | |||
{ | |||
if(style != EdgeGUI.EdgeStyle.Curvy) return; | |||
Vector3[] array; | |||
Vector3[] array2; | |||
GetCurvyConnectorValues(start, end, out array, out array2); | |||
Handles.DrawBezier(array[0], array[1], array2[0], array2[1], color, tex, 8f); | |||
} | |||
else | |||
{ | |||
Vector3[] array; | |||
Vector3[] array2; | |||
GetAngularConnectorValues(start, end, out array, out array2); | |||
DrawRoundedPolyLine(array, array2, tex, color); | |||
} | |||
} | |||
private static void DrawEdge(Vector2 start, Vector2 end, Texture2D tex, Color color, Color endColor, | |||
EdgeGUI.EdgeStyle style) | |||
{ | |||
if(style != EdgeGUI.EdgeStyle.Angular) | |||
{ | |||
if(style != EdgeGUI.EdgeStyle.Curvy) return; | |||
Vector3[] array; | |||
Vector3[] array2; | |||
Vector3[] array3; | |||
Vector3[] array4; | |||
Vector3[] array5; | |||
Vector3[] array6; | |||
GetCurvyConnectorValues(start, end, out array5, out array6); | |||
GetCurvyConnectorValues(start, (start + end) / 2f, out array, out array2); | |||
GetCurvyConnectorValues((start + end) / 2f, end, out array3, out array4); | |||
Handles.DrawBezier(array[0], array[1], array6[0], array2[1], color, tex, 8f); | |||
Handles.DrawBezier(array3[0], array3[1], array4[0], array6[1], endColor, tex, 8f); | |||
} | |||
else | |||
{ | |||
Vector3[] array; | |||
Vector3[] array2; | |||
GetAngularConnectorValues(start, end, out array, out array2); | |||
DrawRoundedPolyLine(array, array2, tex, color); | |||
} | |||
} | |||
private static void GetCurvyConnectorValues(Vector2 start, Vector2 end, out Vector3[] points, | |||
out Vector3[] tangents) | |||
{ | |||
points = new Vector3[] | |||
{ | |||
start, | |||
end | |||
}; | |||
tangents = new Vector3[2]; | |||
float num = 0.5f; | |||
float num2 = 1f - num; | |||
float num3 = 0f; | |||
if(start.x > end.x) | |||
{ | |||
num = (num2 = -0.25f); | |||
float f = (start.x - end.x) / (start.y - end.y); | |||
if(Mathf.Abs(f) > 0.5f) | |||
{ | |||
float num4 = (Mathf.Abs(f) - 0.5f) / 8f; | |||
num4 = Mathf.Sqrt(num4); | |||
num3 = Mathf.Min(num4 * 80f, 80f); | |||
if(start.y > end.y) | |||
{ | |||
num3 = -num3; | |||
} | |||
} | |||
} | |||
float d = Mathf.Clamp01(((start - end).magnitude - 10f) / 50f); | |||
tangents[0] = start + new Vector2((end.x - start.x) * num + 30f, num3) * d; | |||
tangents[1] = end + new Vector2((end.x - start.x) * -num2 - 30f, -num3) * d; | |||
} | |||
private static void GetAngularConnectorValues(Vector2 start, Vector2 end, out Vector3[] points, | |||
out Vector3[] tangents) | |||
{ | |||
Vector2 a = start - end; | |||
Vector2 vector = a / 2f + end; | |||
Vector2 vector2 = new Vector2(Mathf.Sign(a.x), Mathf.Sign(a.y)); | |||
Vector2 vector3 = new Vector2(Mathf.Min(Mathf.Abs(a.x / 2f), 5f), Mathf.Min(Mathf.Abs(a.y / 2f), 5f)); | |||
points = new Vector3[] | |||
{ | |||
start, | |||
new Vector3(vector.x + vector3.x * vector2.x, start.y), | |||
new Vector3(vector.x, start.y - vector3.y * vector2.y), | |||
new Vector3(vector.x, end.y + vector3.y * vector2.y), | |||
new Vector3(vector.x - vector3.x * vector2.x, end.y), | |||
end | |||
}; | |||
tangents = new Vector3[] | |||
{ | |||
(points[1] - points[0]).normalized * vector3.x * 0.6f + points[1], | |||
(points[2] - points[3]).normalized * vector3.y * 0.6f + points[2], | |||
(points[3] - points[2]).normalized * vector3.y * 0.6f + points[3], | |||
(points[4] - points[5]).normalized * vector3.x * 0.6f + points[4] | |||
}; | |||
} | |||
private static void DrawRoundedPolyLine(Vector3[] points, Vector3[] tangents, Texture2D tex, Color color) | |||
{ | |||
Handles.color = color; | |||
for(int i = 0; i < points.Length; i += 2) | |||
{ | |||
Handles.DrawAAPolyLine( | |||
tex, | |||
3f, | |||
new Vector3[] | |||
{ | |||
points[i], | |||
points[i + 1] | |||
}); | |||
} | |||
for(int j = 0; j < tangents.Length; j += 2) | |||
{ | |||
Handles.DrawBezier(points[j + 1], points[j + 2], tangents[j], tangents[j + 1], color, tex, 3f); | |||
} | |||
} | |||
private static void GetEdgeEndPoints(Edge e, out Vector2 start, out Vector2 end) | |||
{ | |||
FieldInfo propInfo = typeof(Slot).GetField( | |||
"m_Position", | |||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); | |||
if(propInfo == null) Debug.LogError("PropInfo m_Position is null!"); | |||
Rect fromSlotPosition = (Rect)propInfo.GetValue(e.fromSlot); | |||
Rect toSlotPosition = (Rect)propInfo.GetValue(e.toSlot); | |||
Assembly unityEngineAssembly = Assembly.GetAssembly(typeof(UnityEngine.GUI)); | |||
Type guiClipType = unityEngineAssembly.GetType("UnityEngine.GUIClip", true); | |||
object[] startArgs = {new Vector2(fromSlotPosition.xMax, fromSlotPosition.y + 9f)}; | |||
object[] endArgs = {new Vector2(toSlotPosition.x, toSlotPosition.y + 9f)}; | |||
ParameterModifier startP = new ParameterModifier(1); | |||
startP[0] = true; | |||
ParameterModifier[] startMods = {startP}; | |||
ParameterModifier endP = new ParameterModifier(1); | |||
endP[0] = true; | |||
ParameterModifier[] endMods = {endP}; | |||
MethodInfo startClipRect = guiClipType.GetMethod( | |||
"Clip", | |||
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy, | |||
Type.DefaultBinder, | |||
new Type[] {typeof(Vector2)}, | |||
startMods); | |||
MethodInfo endClipRect = guiClipType.GetMethod( | |||
"Clip", | |||
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy, | |||
Type.DefaultBinder, | |||
new Type[] {typeof(Vector2)}, | |||
endMods); | |||
start = (Vector2)startClipRect.Invoke(null, startArgs); | |||
end = (Vector2)endClipRect.Invoke(null, endArgs); | |||
} | |||
public void EndDragging() | |||
{ | |||
_sDragSourceSlot = (_sDropTarget = null); | |||
MoveEdge = null; | |||
DontDrawEdge = null; | |||
} | |||
public void EndSlotDragging(Slot slot, bool allowMultiple) | |||
{ | |||
if(slot.isInputSlot && slot.isFlowSlot) | |||
allowMultiple = true; | |||
if(_sDropTarget != slot) return; | |||
if(MoveEdge != null) | |||
{ | |||
slot.node.graph.RemoveEdge(MoveEdge); | |||
} | |||
while(_sDropTarget.edges.Count > 0) | |||
{ | |||
if(allowMultiple) | |||
{ | |||
break; | |||
} | |||
slot.node.graph.RemoveEdge(_sDropTarget.edges[0]); | |||
} | |||
try | |||
{ | |||
slot.node.graph.Connect(_sDragSourceSlot, slot); | |||
} | |||
finally | |||
{ | |||
EndDragging(); | |||
slot.node.graph.Dirty(); | |||
Event.current.Use(); | |||
} | |||
GUIUtility.ExitGUI(); | |||
} | |||
public Edge FindClosestEdge() | |||
{ | |||
Vector2 mousePosition = Event.current.mousePosition; | |||
float num = float.PositiveInfinity; | |||
Edge result = null; | |||
foreach(Edge current in host.graph.edges) | |||
{ | |||
Vector2 start; | |||
Vector2 end; | |||
GetEdgeEndPoints(current, out start, out end); | |||
Vector3[] array; | |||
if(this.edgeStyle == EdgeGUI.EdgeStyle.Angular) | |||
{ | |||
Vector3[] array2; | |||
GetAngularConnectorValues(start, end, out array, out array2); | |||
} | |||
else | |||
{ | |||
Vector3[] array2; | |||
GetCurvyConnectorValues(start, end, out array, out array2); | |||
} | |||
for(int i = 0; i < array.Length; i += 2) | |||
{ | |||
float num2 = HandleUtility.DistancePointLine(mousePosition, array[i], array[i + 1]); | |||
if(!(num2 < num)) continue; | |||
num = num2; | |||
result = current; | |||
} | |||
} | |||
if(num > 10f) | |||
{ | |||
result = null; | |||
} | |||
return result; | |||
} | |||
public void SlotDragging(Slot slot, bool allowEndDrag, bool allowMultiple) | |||
{ | |||
if(slot.isInputSlot && slot.isFlowSlot) | |||
allowMultiple = true; | |||
if(!allowEndDrag || _sDragSourceSlot == null || _sDragSourceSlot == slot) return; | |||
if(_sDropTarget != slot && slot.node.graph.CanConnect(_sDragSourceSlot, slot) && | |||
!slot.node.graph.Connected(_sDragSourceSlot, slot)) | |||
{ | |||
if((slot.node).inputDataEdges.All(e => e != MoveEdge)) | |||
{ | |||
_sDropTarget = slot; | |||
if(slot.edges.Count > 0 && !allowMultiple) | |||
{ | |||
DontDrawEdge = slot.edges[slot.edges.Count - 1]; | |||
} | |||
} | |||
} | |||
Event.current.Use(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 2af90dfd0a2ecde49bd14418e221009c | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,693 @@ | |||
using System; | |||
using System.CodeDom; | |||
using System.Collections.Generic; | |||
using System.ComponentModel; | |||
using System.Globalization; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Text.RegularExpressions; | |||
using Microsoft.CSharp; | |||
using UnityEngine; | |||
using UnityEditor; | |||
using UnityEditor.Graphs; | |||
using VRC.Udon.Common; | |||
using VRC.Udon.Common.Interfaces; | |||
using VRC.Udon.Common.Utils; | |||
using VRC.Udon.Compiler; | |||
using VRC.Udon.EditorBindings; | |||
using VRC.Udon.EditorBindings.Interfaces; | |||
using VRC.Udon.Graph; | |||
using VRC.Udon.Graph.Interfaces; | |||
using VRC.Udon.Serialization; | |||
using VRC.Udon.UAssembly.Assembler; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
public class UdonGraph : UnityEditor.Graphs.Graph | |||
{ | |||
public IUdonGraphDataProvider graphProgramAsset; | |||
public UdonGraphData data; | |||
private readonly string[] _specialFlows = | |||
{ | |||
"Block", | |||
"Branch", | |||
"For", | |||
"Foreach", | |||
"While", | |||
}; | |||
public bool Reloading { get; set; } = false; | |||
internal static string FriendlyTypeName(Type t) | |||
{ | |||
if (t == null) | |||
{ | |||
return "null"; | |||
} | |||
if (!t.IsPrimitive) | |||
{ | |||
if(t == typeof(UnityEngine.Object)) | |||
{ | |||
return "Unity Object"; | |||
} | |||
return t.Name; | |||
} | |||
using (CSharpCodeProvider provider = new CSharpCodeProvider()) | |||
{ | |||
CodeTypeReference typeRef = new CodeTypeReference(t); | |||
return provider.GetTypeOutput(typeRef); | |||
} | |||
} | |||
public UdonNode CreateNode(UdonNodeData nodeData) | |||
{ | |||
UdonNodeDefinition udonNodeDefinition; | |||
try | |||
{ | |||
udonNodeDefinition = UdonEditorManager.Instance.GetNodeDefinition(nodeData.fullName); | |||
} | |||
catch | |||
{ | |||
Debug.LogError($"Skipping missing node: {nodeData.fullName}"); | |||
return null; | |||
} | |||
if (!TrySetupNode(udonNodeDefinition, nodeData.position, out UdonNode node, ref nodeData)) return null; | |||
int connectedFlowCount = nodeData.flowUIDs.Count(f => !string.IsNullOrEmpty(f)); | |||
ValidateNodeData(); | |||
LayoutSlots(udonNodeDefinition, node, connectedFlowCount); | |||
AddNode(node); | |||
return node; | |||
void ValidateNodeData() | |||
{ | |||
bool modifiedData = false; | |||
for (int i = 0; i < nodeData.nodeValues.Length; i++) | |||
{ | |||
if (udonNodeDefinition.Inputs.Count <= i) | |||
{ | |||
continue; | |||
} | |||
Type expectedType = udonNodeDefinition.Inputs[i].type; | |||
if (nodeData.nodeValues[i] == null) | |||
{ | |||
continue; | |||
} | |||
object value = nodeData.nodeValues[i].Deserialize(); | |||
if (value == null) | |||
{ | |||
continue; | |||
} | |||
if (!expectedType.IsInstanceOfType(value)) | |||
{ | |||
nodeData.nodeValues[i] = SerializableObjectContainer.Serialize(null, expectedType); | |||
modifiedData = true; | |||
} | |||
} | |||
if (modifiedData) | |||
{ | |||
ReSerializeData(); | |||
} | |||
} | |||
} | |||
public void CreateNode(UdonNodeDefinition udonNodeDefinition, Vector2? position = null) | |||
{ | |||
UdonNodeData nodeData = null; | |||
if (!TrySetupNode(udonNodeDefinition, position, out UdonNode node, ref nodeData)) return; | |||
PopulateDefaultValues(); | |||
LayoutSlots(udonNodeDefinition, node, 0); | |||
ReSerializeData(); | |||
AddNode(node); | |||
void PopulateDefaultValues() | |||
{ | |||
if (udonNodeDefinition.defaultValues == null) return; | |||
nodeData.nodeValues = new SerializableObjectContainer[udonNodeDefinition.defaultValues.Count]; | |||
nodeData.nodeUIDs = new string[udonNodeDefinition.defaultValues.Count]; | |||
for (int i = 0; i < udonNodeDefinition.defaultValues.Count; i++) | |||
{ | |||
object defaultValue = udonNodeDefinition.defaultValues[i]; | |||
if (defaultValue != null) | |||
{ | |||
nodeData.nodeValues[i] = SerializableObjectContainer.Serialize(defaultValue); | |||
} | |||
} | |||
} | |||
} | |||
private bool TrySetupNode(UdonNodeDefinition udonNodeDefinition, Vector2? position, out UdonNode node, | |||
ref UdonNodeData nodeData) | |||
{ | |||
DoPropHack(); | |||
if (!TryCreateNodeInstance(out node)) return false; | |||
if (nodeData == null) | |||
{ | |||
nodeData = data.AddNode(udonNodeDefinition.fullName); | |||
} | |||
node.uid = nodeData.uid; | |||
return true; | |||
void DoPropHack() | |||
{ | |||
//Awful hack to fix regression in unity graph property type conversion | |||
{ | |||
FieldInfo prop = typeof(TypeConverter).GetField( | |||
"useCompatibleTypeConversion", | |||
BindingFlags.NonPublic | BindingFlags.Static | |||
); | |||
if (prop != null) prop.SetValue(this, true); | |||
} | |||
} | |||
bool TryCreateNodeInstance(out UdonNode outNode) | |||
{ | |||
outNode = CreateInstance<UdonNode>(); | |||
outNode.name = udonNodeDefinition.fullName; | |||
outNode.title = PrettyString(udonNodeDefinition.name).FriendlyNameify(); | |||
outNode.position = position == null ? new Rect(Vector2.zero, Vector2.zero) : new Rect(position.Value, Vector2.zero); | |||
string nodeName = outNode.name; | |||
if (nodeName.StartsWith("Event_") && | |||
(nodeName != "Event_Custom" || graphProgramAsset.GetType() == typeof(UdonSubGraphAsset))) | |||
{ | |||
if (nodes.Any(n => n.name == nodeName)) | |||
{ | |||
Debug.LogWarning( | |||
$"Can't create more than one {nodeName} node, try managing your flow with a Block node instead!"); | |||
return false; | |||
} | |||
} | |||
if (nodeName.StartsWith("Event_") && | |||
(nodeName != "Event_Custom" && graphProgramAsset.GetType() == typeof(UdonSubGraphAsset))) | |||
{ | |||
Debug.LogWarning($"SubGraphs can't use built-in events, pipe in your event from the parent graph instead!"); | |||
return false; | |||
} | |||
if (outNode.title == "Const_VRCUdonCommonInterfacesIUdonEventReceiver") | |||
{ | |||
outNode.title = "UdonBehaviour"; | |||
} | |||
return true; | |||
} | |||
} | |||
private void LayoutSlots(UdonNodeDefinition udonNodeDefinition, UdonNode node, int connectedFlowCount) | |||
{ | |||
//Layout Flow Slots | |||
if (udonNodeDefinition.flow) | |||
{ | |||
if (!udonNodeDefinition.fullName.StartsWith("Event_")) | |||
{ | |||
node.AddInputSlot(""); | |||
} | |||
node.AddOutputSlot(""); | |||
if (_specialFlows.Contains(udonNodeDefinition.fullName)) | |||
{ | |||
node.AddOutputSlot(""); | |||
} | |||
if (udonNodeDefinition.fullName == "Block") | |||
{ | |||
int connectedFlows = connectedFlowCount; | |||
if (connectedFlows > 1) | |||
{ | |||
for (int i = 0; i < connectedFlows - 1; i++) | |||
{ | |||
node.AddOutputSlot(""); | |||
} | |||
} | |||
} | |||
} | |||
//Layout InOut Slots | |||
for (int index = 0; index < udonNodeDefinition.Inputs.Count; index++) | |||
{ | |||
UdonNodeParameter input = udonNodeDefinition.Inputs[index]; | |||
string label = ""; | |||
if (udonNodeDefinition.Inputs.Count > index && index >= 0) | |||
{ | |||
label = udonNodeDefinition.Inputs[index].name; | |||
} | |||
if (label == "IUdonEventReceiver") | |||
{ | |||
label = "UdonBehaviour"; | |||
} | |||
label = label.FriendlyNameify(); | |||
Slot slot = node.AddInputSlot(FriendlyTypeName(input.type), | |||
SlotTypeConverter(input.type, udonNodeDefinition.fullName)); | |||
slot.title = label; | |||
} | |||
foreach (UdonNodeParameter output in udonNodeDefinition.Outputs) | |||
{ | |||
node.AddOutputSlot(FriendlyTypeName(output.type), SlotTypeConverter(output.type, udonNodeDefinition.fullName)); | |||
} | |||
} | |||
private static Type SlotTypeConverter(Type type, string fullName) | |||
{ | |||
if(type == null) | |||
{ | |||
return typeof(object); | |||
} | |||
if (fullName.Contains("IUdonEventReceiver") && type == typeof(UnityEngine.Object)) | |||
{ | |||
return typeof(UdonBehaviour); | |||
} | |||
return type; | |||
} | |||
public void DeleteNode(string nodeID) | |||
{ | |||
UdonNodeData node = data.FindNode(nodeID); | |||
if (node == null) | |||
{ | |||
return; | |||
} | |||
data.RemoveNode(node); | |||
ReSerializeData(); | |||
} | |||
public override Edge Connect(Slot fromSlot, Slot toSlot) | |||
{ | |||
int index = 0; | |||
int indexOther = 0; | |||
if (fromSlot.isFlowSlot) | |||
{ | |||
foreach(Slot outputSlot in fromSlot.node.outputFlowSlots) | |||
{ | |||
if(outputSlot == fromSlot) | |||
{ | |||
break; | |||
} | |||
index++; | |||
} | |||
} | |||
else | |||
{ | |||
foreach(Slot inputSlot in toSlot.node.inputDataSlots) | |||
{ | |||
if(inputSlot == toSlot) | |||
{ | |||
break; | |||
} | |||
index++; | |||
} | |||
foreach(Slot outputSlot in fromSlot.node.outputDataSlots) | |||
{ | |||
if(outputSlot == fromSlot) | |||
{ | |||
break; | |||
} | |||
indexOther++; | |||
} | |||
} | |||
UdonNodeData fromNode = data.FindNode(((UdonNode)fromSlot.node).uid); | |||
UdonNodeData toNode = data.FindNode(((UdonNode)toSlot.node).uid); | |||
if(fromSlot.isFlowSlot) | |||
{ | |||
fromNode.AddFlowNode(toNode, index); | |||
} | |||
else | |||
{ | |||
toNode.AddNode(fromNode, index, indexOther); | |||
} | |||
if (fromNode.fullName == "Block") | |||
{ | |||
int connectedFlows = fromNode.flowUIDs.Count(f => !string.IsNullOrEmpty(f)); | |||
if (connectedFlows >= fromSlot.node.outputFlowSlots.Count()) | |||
{ | |||
fromSlot.node.AddOutputSlot(""); | |||
} | |||
} | |||
ReSerializeData(); | |||
return base.Connect(fromSlot, toSlot); | |||
} | |||
public override void RemoveEdge(Edge e) | |||
{ | |||
int index = 0; | |||
if (e.fromSlot.isFlowSlot) | |||
{ | |||
foreach(Slot outputSlot in e.fromSlot.node.outputFlowSlots) | |||
{ | |||
if(outputSlot == e.fromSlot) | |||
{ | |||
break; | |||
} | |||
index++; | |||
} | |||
} | |||
else | |||
{ | |||
foreach(Slot inputSlot in e.toSlot.node.inputDataSlots) | |||
{ | |||
if(inputSlot == e.toSlot) | |||
{ | |||
break; | |||
} | |||
index++; | |||
} | |||
} | |||
UdonNodeData toNode = data.FindNode(((UdonNode)e.toSlot.node).uid); | |||
UdonNodeData fromNode = data.FindNode(((UdonNode)e.fromSlot.node).uid); | |||
if(e.fromSlot.isFlowSlot) | |||
{ | |||
fromNode.RemoveFlowNode(index); | |||
} | |||
else | |||
{ | |||
toNode.RemoveNode(index); | |||
} | |||
ReSerializeData(); | |||
base.RemoveEdge(e); | |||
} | |||
public override bool CanConnect(Slot fromSlot, Slot toSlot) | |||
{ | |||
if(fromSlot.node == toSlot.node) | |||
{ | |||
return false; | |||
} | |||
if(fromSlot.isFlowSlot && toSlot.isFlowSlot) | |||
{ | |||
return FindRecursiveFlow(fromSlot, toSlot); | |||
//return fromSlot.edges.Count <= 0 && toSlot.edges.Count <= 0 && FindRecursiveFlow(fromSlot, toSlot); | |||
} | |||
if(fromSlot.isFlowSlot && !toSlot.isFlowSlot) | |||
{ | |||
return false; | |||
} | |||
if(!fromSlot.isFlowSlot && toSlot.isFlowSlot) | |||
{ | |||
return false; | |||
} | |||
if (toSlot.dataType.IsAssignableFrom(fromSlot.dataType) || | |||
fromSlot.dataType.IsAssignableFrom(toSlot.dataType)) | |||
{ | |||
return true; | |||
} | |||
if (fromSlot.node.name.Contains("__T") || fromSlot.node.name.Contains("__TArray")) | |||
{ | |||
return true; | |||
} | |||
return false; | |||
} | |||
private static bool FindRecursiveFlow(Slot fromSlot, Slot toSlot) | |||
{ | |||
foreach(Edge edge in toSlot.node.outputFlowEdges) | |||
{ | |||
if(edge.toSlot.node == fromSlot.node) | |||
{ | |||
return false; | |||
} | |||
if(!FindRecursiveFlow(fromSlot, edge.toSlot)) | |||
{ | |||
return false; | |||
} | |||
} | |||
return true; | |||
} | |||
public void Reload() | |||
{ | |||
if (this == null) | |||
{ | |||
DestroyImmediate(this); | |||
return; | |||
} | |||
Reloading = true; | |||
// ReSharper disable once DelegateSubtraction | |||
Undo.undoRedoPerformed -= OnUndoRedo; //Remove old handler if present to prevent duplicates, doesn't cause errors if not present | |||
Undo.undoRedoPerformed += OnUndoRedo; | |||
nodes.Clear(); | |||
edges.Clear(); | |||
IEnumerable<UdonNodeDefinition> definitions = UdonEditorManager.Instance.GetNodeDefinitions(); | |||
if (definitions == null || definitions.Count() < 100) | |||
{ | |||
throw new NullReferenceException("Udon NodeDefinitions have failed to load, aborting graph load."); | |||
} | |||
for (int i = data.nodes.Count - 1; i >= 0; i--) | |||
{ | |||
UdonNodeData node = data.nodes[i]; | |||
UdonNode udonNode = CreateNode(node); | |||
if (udonNode != null) continue; | |||
Debug.Log($"Removing null node '{node.fullName}'"); | |||
data.nodes.RemoveAt(i); | |||
} | |||
foreach(Node node in nodes) | |||
{ | |||
UdonNode udonNode = (UdonNode)node; | |||
udonNode.PopulateEdges(); | |||
} | |||
Reloading = false; | |||
ReSerializeData(); | |||
} | |||
private void OnUndoRedo() | |||
{ | |||
data = new UdonGraphData(graphProgramAsset.GetGraphData()); | |||
Reload(); | |||
} | |||
public override void RemoveNode(Node node, bool destroyNode = false) | |||
{ | |||
if (node == null) | |||
{ | |||
return; | |||
} | |||
base.RemoveNode(node, destroyNode); | |||
} | |||
public void ReSerializeData() | |||
{ | |||
if (Reloading) | |||
{ | |||
return; | |||
} | |||
SerializedObject serializedGraphProgramAsset; | |||
if (graphProgramAsset.GetType() == typeof(UdonGraphProgramAsset)) | |||
{ | |||
serializedGraphProgramAsset = new SerializedObject((UdonGraphProgramAsset)graphProgramAsset); | |||
} | |||
else | |||
{ | |||
serializedGraphProgramAsset = new SerializedObject((UdonSubGraphAsset)graphProgramAsset); | |||
} | |||
SerializedProperty graphDataProperty = serializedGraphProgramAsset.FindProperty("graphData"); | |||
SerializedProperty nodesProperty = graphDataProperty.FindPropertyRelative("nodes"); | |||
if(nodesProperty.arraySize > data.nodes.Count) | |||
{ | |||
nodesProperty.ClearArray(); | |||
} | |||
for(int i = 0; i < data.nodes.Count; i++) | |||
{ | |||
if(nodesProperty.arraySize < data.nodes.Count) | |||
{ | |||
nodesProperty.InsertArrayElementAtIndex(i); | |||
} | |||
SerializedProperty nodeProperty = nodesProperty.GetArrayElementAtIndex(i); | |||
SerializedProperty fullNameProperty = nodeProperty.FindPropertyRelative("fullName"); | |||
fullNameProperty.stringValue = data.nodes[i].fullName; | |||
SerializedProperty uidProperty = nodeProperty.FindPropertyRelative("uid"); | |||
uidProperty.stringValue = data.nodes[i].uid; | |||
SerializedProperty positionProperty = nodeProperty.FindPropertyRelative("position"); | |||
positionProperty.vector2Value = data.nodes[i].position; | |||
SerializedProperty nodeUIDsProperty = nodeProperty.FindPropertyRelative("nodeUIDs"); | |||
while(nodeUIDsProperty.arraySize > data.nodes[i].nodeUIDs.Length) | |||
{ | |||
nodeUIDsProperty.DeleteArrayElementAtIndex(nodeUIDsProperty.arraySize - 1); | |||
} | |||
for(int j = 0; j < data.nodes[i].nodeUIDs.Length; j++) | |||
{ | |||
if(nodeUIDsProperty.arraySize < data.nodes[i].nodeUIDs.Length) | |||
{ | |||
nodeUIDsProperty.InsertArrayElementAtIndex(j); | |||
nodeUIDsProperty.GetArrayElementAtIndex(j).stringValue = ""; | |||
} | |||
SerializedProperty nodeUIDProperty = nodeUIDsProperty.GetArrayElementAtIndex(j); | |||
nodeUIDProperty.stringValue = data.nodes[i].nodeUIDs[j]; | |||
} | |||
SerializedProperty flowUIDsProperty = nodeProperty.FindPropertyRelative("flowUIDs"); | |||
while(flowUIDsProperty.arraySize > data.nodes[i].flowUIDs.Length) | |||
{ | |||
flowUIDsProperty.DeleteArrayElementAtIndex(flowUIDsProperty.arraySize - 1); | |||
} | |||
for(int j = 0; j < data.nodes[i].flowUIDs.Length; j++) | |||
{ | |||
if(flowUIDsProperty.arraySize < data.nodes[i].flowUIDs.Length) | |||
{ | |||
flowUIDsProperty.InsertArrayElementAtIndex(j); | |||
flowUIDsProperty.GetArrayElementAtIndex(j).stringValue = ""; | |||
} | |||
SerializedProperty flowUIDProperty = flowUIDsProperty.GetArrayElementAtIndex(j); | |||
flowUIDProperty.stringValue = data.nodes[i].flowUIDs[j]; | |||
} | |||
SerializedProperty nodeValuesProperty = nodeProperty.FindPropertyRelative("nodeValues"); | |||
while(nodeValuesProperty.arraySize > data.nodes[i].nodeValues.Length) | |||
{ | |||
nodeValuesProperty.DeleteArrayElementAtIndex(nodeValuesProperty.arraySize - 1); | |||
} | |||
for(int j = 0; j < data.nodes[i].nodeValues.Length; j++) | |||
{ | |||
if(nodeValuesProperty.arraySize < data.nodes[i].nodeValues.Length) | |||
{ | |||
nodeValuesProperty.InsertArrayElementAtIndex(j); | |||
nodeValuesProperty.GetArrayElementAtIndex(j).FindPropertyRelative("unityObjectValue").objectReferenceValue = null; | |||
nodeValuesProperty.GetArrayElementAtIndex(j).FindPropertyRelative("stringValue").stringValue = ""; | |||
} | |||
SerializedProperty nodeValueProperty = nodeValuesProperty.GetArrayElementAtIndex(j); | |||
if (data.nodes[i].nodeValues[j] == null) | |||
{ | |||
continue; | |||
} | |||
object nodeValue = data.nodes[i].nodeValues[j].Deserialize(); | |||
if (nodeValue != null) | |||
{ | |||
if (nodeValue is UnityEngine.Object value) | |||
{ | |||
if (value != null) | |||
{ | |||
nodeValueProperty.FindPropertyRelative("unityObjectValue").objectReferenceValue = | |||
data.nodes[i].nodeValues[j].unityObjectValue; | |||
} | |||
} | |||
} | |||
nodeValueProperty.FindPropertyRelative("stringValue").stringValue = | |||
data.nodes[i].nodeValues[j].stringValue; | |||
} | |||
} | |||
serializedGraphProgramAsset.ApplyModifiedProperties(); | |||
if (graphProgramAsset is AbstractUdonProgramSource udonProgramSource) | |||
{ | |||
UdonEditorManager.Instance.QueueProgramSourceRefresh(udonProgramSource); | |||
} | |||
} | |||
public void UpdateNodePosition(UdonNode node) | |||
{ | |||
data.FindNode(node.uid).position = node.position.position; | |||
ReSerializeData(); | |||
} | |||
private static string PrettyString(string s) | |||
{ | |||
switch(s) | |||
{ | |||
case "op_Equality": | |||
s = "=="; | |||
break; | |||
case "op_Inequality": | |||
s = "!="; | |||
break; | |||
case "op_Addition": | |||
s = "+"; | |||
break; | |||
case "VRCUdonCommonInterfacesIUdonEventReceiver": | |||
s = "UdonBehaviour"; | |||
break; | |||
// ReSharper disable once RedundantEmptySwitchSection | |||
default: | |||
break; | |||
} | |||
s = s.Replace("_", " "); | |||
s = ParseByCase(s); | |||
TextInfo textInfo = new CultureInfo("en-US", false).TextInfo; | |||
return textInfo.ToTitleCase(s); | |||
} | |||
private static string ParseByCase(string strInput) | |||
{ | |||
string strOutput = ""; | |||
int intCurrentCharPos = 0; | |||
int intLastCharPos = strInput.Length - 1; | |||
for(intCurrentCharPos = 0; intCurrentCharPos <= intLastCharPos; intCurrentCharPos++) | |||
{ | |||
char chrCurrentInputChar = strInput[intCurrentCharPos]; | |||
char chrPreviousInputChar = chrCurrentInputChar; | |||
if(intCurrentCharPos > 0) | |||
{ | |||
chrPreviousInputChar = strInput[intCurrentCharPos - 1]; | |||
} | |||
if(char.IsUpper(chrCurrentInputChar) && char.IsLower(chrPreviousInputChar)) | |||
{ | |||
strOutput += " "; | |||
} | |||
strOutput += chrCurrentInputChar; | |||
} | |||
return strOutput; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 30dac15540ec41545a204d3558107018 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,224 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using VRC.Udon.Graph; | |||
using VRC.Udon.Graph.Interfaces; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
internal static class UdonGraphExtensions | |||
{ | |||
private static readonly Dictionary<string, string> FriendlyNameCache; | |||
static UdonGraphExtensions() | |||
{ | |||
FriendlyNameCache = new Dictionary<string, string>(); | |||
StartsWithCache = new Dictionary<(string s, string prefix), bool>(); | |||
} | |||
public static string FriendlyNameify(this string typeString) | |||
{ | |||
if (typeString == null) | |||
{ | |||
return null; | |||
} | |||
if (FriendlyNameCache.ContainsKey(typeString)) | |||
{ | |||
return FriendlyNameCache[typeString]; | |||
} | |||
string originalString = typeString; | |||
typeString = typeString.Replace("Single", "float"); | |||
typeString = typeString.Replace("Int32", "int"); | |||
typeString = typeString.Replace("String", "string"); | |||
typeString = typeString.Replace("VRCUdonCommonInterfacesIUdonEventReceiver", "UdonBehaviour"); | |||
typeString = typeString.Replace("IUdonEventReceiver", "UdonBehaviour"); | |||
typeString = typeString.Replace("Const_VRCUdonCommonInterfacesIUdonEventReceiver", "UdonBehaviour"); | |||
typeString = typeString.Replace("Array", "[]"); | |||
// ReSharper disable once StringLiteralTypo | |||
if (typeString.Replace("ector", "").Contains("ctor")) //Handle "Vector/vector" | |||
{ | |||
typeString = typeString.ReplaceLast("ctor", "constructor"); | |||
} | |||
if (typeString == "IUdonEventReceiver") | |||
{ | |||
typeString = "UdonBehaviour"; | |||
} | |||
FriendlyNameCache.Add(originalString, typeString); | |||
return typeString; | |||
} | |||
private static readonly Dictionary<(string s, string prefix), bool> StartsWithCache; | |||
public static bool StartsWithCached(this string s, string prefix) | |||
{ | |||
if (StartsWithCache.ContainsKey((s, prefix))) | |||
{ | |||
return StartsWithCache[(s, prefix)]; | |||
} | |||
bool doesStartWith = s.StartsWith(prefix); | |||
StartsWithCache.Add((s, prefix), doesStartWith); | |||
return doesStartWith; | |||
} | |||
static string UppercaseFirst(this string s) | |||
{ | |||
if (string.IsNullOrEmpty(s)) | |||
{ | |||
return string.Empty; | |||
} | |||
char[] a = s.ToCharArray(); | |||
a[0] = char.ToUpper(a[0]); | |||
return new string(a); | |||
} | |||
public static string ReplaceFirst(this string text, string search, string replace) | |||
{ | |||
int pos = text.IndexOf(search, StringComparison.Ordinal); | |||
if (pos < 0) | |||
{ | |||
return text; | |||
} | |||
return text.Substring(0, pos) + replace + text.Substring(pos + search.Length); | |||
} | |||
public static string ReplaceLast(this string source, string find, string replace) | |||
{ | |||
int place = source.LastIndexOf(find, StringComparison.Ordinal); | |||
if(place == -1) | |||
return source; | |||
string result = source.Remove(place, find.Length).Insert(place, replace); | |||
return result; | |||
} | |||
public static UdonNodeSearchMenu.NodeMenuLayer FindLayer(this UdonNodeSearchMenu.NodeMenuLayer layer, string activePath) | |||
{ | |||
if (string.IsNullOrEmpty(activePath) || activePath == "/") | |||
return layer; | |||
return layer.MenuName == activePath | |||
? layer | |||
: layer.SubNodes.First(n => n.MenuName == activePath.Split('/')[0]) | |||
.FindLayer(string.Join("/", activePath.Split('/').Skip(1).ToArray())); | |||
} | |||
public static void PopulateNodeMenu(this UdonNodeSearchMenu.NodeMenuLayer nodeLayers) | |||
{ | |||
List<(string path, UdonNodeDefinition nodeDefinition)> nodePaths = new List<(string path, UdonNodeDefinition nodeDefinition)>(); | |||
foreach (KeyValuePair<string, INodeRegistry> topRegistry in UdonEditorManager.Instance.GetNodeRegistries()) | |||
{ | |||
string topName = topRegistry.Key.Replace("NodeRegistry", ""); | |||
foreach (KeyValuePair<string, INodeRegistry> registry in topRegistry.Value.GetNodeRegistries().OrderBy(s => s.Key)) | |||
{ | |||
string baseRegistryName = registry.Key.Replace("NodeRegistry", "").FriendlyNameify().ReplaceFirst(topName, ""); | |||
string registryName = baseRegistryName.UppercaseFirst(); | |||
if (topName == "Udon" && (registryName == "Event" || registryName == "Type")) | |||
{ | |||
registryName = $"{registryName}s"; | |||
} | |||
if (registryName.EndsWith("[]")) | |||
{ | |||
registryName = $"{registryName.Substring(0, registryName.Length - 2)}/{registryName}"; | |||
} | |||
Dictionary<string, UdonNodeDefinition> baseNodeDefinition = new Dictionary<string, UdonNodeDefinition>(); | |||
foreach (UdonNodeDefinition nodeDefinition in registry.Value.GetNodeDefinitions().OrderBy(s => UdonNodeSearchMenu.PrettyFullName(s))) | |||
{ | |||
string baseIdentifier = nodeDefinition.fullName; | |||
string[] splitBaseIdentifier = baseIdentifier.Split(new[] {"__"}, StringSplitOptions.None); | |||
if (splitBaseIdentifier.Length >= 2) | |||
{ | |||
baseIdentifier = $"{splitBaseIdentifier[0]}__{splitBaseIdentifier[1]}"; | |||
} | |||
if (baseNodeDefinition.ContainsKey(baseIdentifier)) | |||
{ | |||
continue; | |||
} | |||
baseNodeDefinition.Add(baseIdentifier, nodeDefinition); | |||
} | |||
foreach (KeyValuePair<string, UdonNodeDefinition> nodeDefinitionsEntry in baseNodeDefinition) | |||
{ | |||
string nodeName = PrettyBaseName(nodeDefinitionsEntry.Key).ReplaceFirst(baseRegistryName, ""); | |||
if (nodeName.Contains(".")) | |||
{ | |||
nodeName = nodeName.Split('.')[1]; | |||
} | |||
nodeName = nodeName.UppercaseFirst(); | |||
if(topName == "Udon") | |||
{ | |||
nodePaths.Add(( | |||
$"{registryName}/{nodeName}", nodeDefinitionsEntry.Value)); | |||
} | |||
else | |||
{ | |||
nodePaths.Add(( | |||
$"{topName}/{registryName}/{nodeName}", nodeDefinitionsEntry.Value)); | |||
} | |||
} | |||
} | |||
} | |||
foreach ((string path, UdonNodeDefinition nodeDefinition) item in nodePaths) | |||
{ | |||
NodeMenuBuilder(item.path, item.nodeDefinition, nodeLayers); | |||
} | |||
nodeLayers.MenuName = ""; | |||
void NodeMenuBuilder(string path, UdonNodeDefinition nodeDefinition, UdonNodeSearchMenu.NodeMenuLayer container) | |||
{ | |||
string[] segments = path.Split('/'); | |||
string head = segments[0]; | |||
string[] tail = segments.Skip(1).ToArray(); | |||
if (tail.Length == 0) | |||
{ | |||
if (container.SubNodes.Any(n => n.MenuName == head)) | |||
{ | |||
container.SubNodes.First(n => n.MenuName == head).NodeDefinition = nodeDefinition; | |||
} | |||
else | |||
{ | |||
UdonNodeSearchMenu.NodeMenuLayer nLayer = new UdonNodeSearchMenu.NodeMenuLayer(); | |||
nLayer.MenuName = head; | |||
nLayer.NodeDefinition = nodeDefinition; | |||
container.SubNodes.Add(nLayer); | |||
} | |||
} | |||
else | |||
{ | |||
string head1 = head; | |||
if (container.SubNodes.All(n => n.MenuName != head1)) | |||
{ | |||
UdonNodeSearchMenu.NodeMenuLayer nLayer = new UdonNodeSearchMenu.NodeMenuLayer(); | |||
nLayer.MenuName = head; | |||
container.SubNodes.Add(nLayer); | |||
} | |||
foreach (UdonNodeSearchMenu.NodeMenuLayer layer in container.SubNodes) | |||
{ | |||
if (layer.MenuName == head) | |||
NodeMenuBuilder(string.Join("/", tail), nodeDefinition, layer); | |||
} | |||
} | |||
} | |||
string PrettyBaseName(string baseIdentifier) | |||
{ | |||
string result = baseIdentifier.Replace("UnityEngine", "").Replace("System", ""); | |||
string[] resultSplit = result.Split(new[] {"__"}, StringSplitOptions.None); | |||
if (resultSplit.Length >= 2) | |||
{ | |||
result = $"{resultSplit[0]}{resultSplit[1]}"; | |||
} | |||
result = result.FriendlyNameify(); | |||
result = result.Replace("op_", ""); | |||
result = result.Replace("_", " "); | |||
return result; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,3 @@ | |||
fileFormatVersion: 2 | |||
guid: 83886f1f93224cfb97ea7f32df366c57 | |||
timeCreated: 1575758808 |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 3f90c33ba12f0134683370bde48be7f2 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,194 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using JetBrains.Annotations; | |||
using UnityEditor; | |||
using UnityEngine; | |||
using VRC.Udon.Common.Interfaces; | |||
using VRC.Udon.Editor.ProgramSources; | |||
using VRC.Udon.Editor.ProgramSources.Attributes; | |||
using VRC.Udon.Graph; | |||
using VRC.Udon.Graph.Interfaces; | |||
using VRC.Udon.Serialization.OdinSerializer; | |||
[assembly: UdonProgramSourceNewMenu(typeof(UdonGraphProgramAsset), "Udon Graph Program Asset")] | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
[CreateAssetMenu(menuName = "VRChat/Udon/Udon Graph Program Asset", fileName = "New Udon Graph Program Asset")] | |||
public class UdonGraphProgramAsset : UdonAssemblyProgramAsset, IUdonGraphDataProvider | |||
{ | |||
[SerializeField] | |||
public UdonGraphData graphData = new UdonGraphData(); | |||
[SerializeField] | |||
private bool showAssembly = false; | |||
[NonSerialized, OdinSerialize] | |||
private Dictionary<string, (object value, Type type)> heapDefaultValues = new Dictionary<string, (object value, Type type)>(); | |||
protected override void DrawProgramSourceGUI(UdonBehaviour udonBehaviour, ref bool dirty) | |||
{ | |||
if(GUILayout.Button("Open Udon Graph", "LargeButton")) | |||
{ | |||
var window = EditorWindow.GetWindow<UdonGraphWindow>("Udon Graph", true, typeof(SceneView)); | |||
window.lastClickedProgramSource = this; | |||
} | |||
DrawPublicVariables(udonBehaviour, ref dirty); | |||
DrawAssemblyErrorTextArea(); | |||
DrawAssemblyTextArea(false, ref dirty); | |||
} | |||
protected override void RefreshProgramImpl() | |||
{ | |||
if(graphData == null) | |||
{ | |||
return; | |||
} | |||
CompileGraph(); | |||
base.RefreshProgramImpl(); | |||
ApplyDefaultValuesToHeap(); | |||
} | |||
protected override void DrawAssemblyTextArea(bool allowEditing, ref bool dirty) | |||
{ | |||
EditorGUI.BeginChangeCheck(); | |||
bool newShowAssembly = EditorGUILayout.Foldout(showAssembly, "Compiled Graph Assembly"); | |||
if(EditorGUI.EndChangeCheck()) | |||
{ | |||
Undo.RecordObject(this, "Toggle Assembly Foldout"); | |||
showAssembly = newShowAssembly; | |||
} | |||
if(!showAssembly) | |||
{ | |||
return; | |||
} | |||
EditorGUI.indentLevel++; | |||
base.DrawAssemblyTextArea(allowEditing, ref dirty); | |||
EditorGUI.indentLevel--; | |||
} | |||
[PublicAPI] | |||
protected void CompileGraph() | |||
{ | |||
udonAssembly = UdonEditorManager.Instance.CompileGraph(graphData, null, out Dictionary<string, (string uid, string fullName, int index)> _, out heapDefaultValues); | |||
} | |||
[PublicAPI] | |||
protected void ApplyDefaultValuesToHeap() | |||
{ | |||
IUdonSymbolTable symbolTable = program?.SymbolTable; | |||
IUdonHeap heap = program?.Heap; | |||
if(symbolTable == null || heap == null) | |||
{ | |||
return; | |||
} | |||
foreach(KeyValuePair<string, (object value, Type type)> defaultValue in heapDefaultValues) | |||
{ | |||
if(!symbolTable.HasAddressForSymbol(defaultValue.Key)) | |||
{ | |||
continue; | |||
} | |||
uint symbolAddress = symbolTable.GetAddressFromSymbol(defaultValue.Key); | |||
(object value, Type declaredType) = defaultValue.Value; | |||
if(typeof(UnityEngine.Object).IsAssignableFrom(declaredType)) | |||
{ | |||
if(value != null && !declaredType.IsInstanceOfType(value)) | |||
{ | |||
heap.SetHeapVariable(symbolAddress, null, declaredType); | |||
continue; | |||
} | |||
if((UnityEngine.Object)value == null) | |||
{ | |||
heap.SetHeapVariable(symbolAddress, null, declaredType); | |||
continue; | |||
} | |||
} | |||
if(value != null) | |||
{ | |||
if(!declaredType.IsInstanceOfType(value)) | |||
{ | |||
value = declaredType.IsValueType ? Activator.CreateInstance(declaredType) : null; | |||
} | |||
} | |||
heap.SetHeapVariable(symbolAddress, value, declaredType); | |||
} | |||
} | |||
protected override object GetPublicVariableDefaultValue(string symbol, Type type) | |||
{ | |||
IUdonSymbolTable symbolTable = program?.SymbolTable; | |||
IUdonHeap heap = program?.Heap; | |||
if(symbolTable == null || heap == null) | |||
{ | |||
return null; | |||
} | |||
if(!heapDefaultValues.ContainsKey(symbol)) | |||
{ | |||
return null; | |||
} | |||
(object value, Type declaredType) = heapDefaultValues[symbol]; | |||
if(!typeof(UnityEngine.Object).IsAssignableFrom(declaredType)) | |||
{ | |||
return value; | |||
} | |||
return (UnityEngine.Object)value == null ? null : value; | |||
} | |||
protected override object DrawPublicVariableField(string symbol, object variableValue, Type variableType, ref bool dirty, | |||
bool enabled) | |||
{ | |||
EditorGUILayout.BeginHorizontal(); | |||
variableValue = base.DrawPublicVariableField(symbol, variableValue, variableType, ref dirty, enabled); | |||
object defaultValue = null; | |||
if(heapDefaultValues.ContainsKey(symbol)) | |||
{ | |||
defaultValue = heapDefaultValues[symbol].value; | |||
} | |||
if(variableValue == null || !variableValue.Equals(defaultValue)) | |||
{ | |||
if(defaultValue != null || variableValue != null) | |||
{ | |||
if(GUILayout.Button("Reset to Default Value")) | |||
{ | |||
variableValue = defaultValue; | |||
dirty = true; | |||
} | |||
} | |||
} | |||
EditorGUILayout.EndHorizontal(); | |||
return variableValue; | |||
} | |||
#region Serialization Methods | |||
protected override void OnAfterDeserialize() | |||
{ | |||
foreach(UdonNodeData node in graphData.nodes) | |||
{ | |||
node.SetGraph(graphData); | |||
} | |||
} | |||
#endregion | |||
public UdonGraphData GetGraphData() | |||
{ | |||
return graphData; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 4f11136daadff0b44ac2278a314682ab | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,9 @@ | |||
using UnityEditor; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
[CustomEditor(typeof(UdonGraphProgramAsset))] | |||
public class UdonGraphProgramAssetEditor : UdonAssemblyProgramAssetEditor | |||
{ | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 31d6811854f59254aa1a263a8d566eb2 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,253 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using UnityEngine; | |||
using UnityEditor; | |||
using VRC.Udon.Graph; | |||
using VRC.Udon.Graph.Interfaces; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
public class UdonGraphWindow : EditorWindow | |||
{ | |||
private const int TOOLBAR_HEIGHT = 17; | |||
private const float CONTENT_LOGO_SCALE = .75f; | |||
[SerializeField] | |||
private UdonGraph graph; | |||
[SerializeField] | |||
private UdonGraphGUI graphGUI; | |||
private static GUIStyle _udonLogo; | |||
public AbstractUdonProgramSource lastClickedProgramSource { private get; set; } | |||
[MenuItem("VRChat SDK/Udon Graph")] | |||
private static void Init() | |||
{ | |||
GetWindow(typeof(UdonGraphWindow)); | |||
} | |||
private void OnEnable() | |||
{ | |||
titleContent = new GUIContent("Udon Graph"); | |||
graph = CreateInstance<UdonGraph>(); | |||
graphGUI = CreateInstance<UdonGraphGUI>(); | |||
graphGUI.graph = graph; | |||
Texture2D logoTexture = Resources.Load<Texture2D>(EditorGUIUtility.isProSkin ? "UdonLogoAlphaWhite" : "UdonLogoAlpha"); | |||
_udonLogo = new GUIStyle | |||
{ | |||
normal = | |||
{ | |||
background = logoTexture, | |||
textColor = Color.white | |||
}, | |||
fixedHeight = (int)(logoTexture.height * CONTENT_LOGO_SCALE), | |||
fixedWidth = (int)(logoTexture.width * CONTENT_LOGO_SCALE) | |||
}; | |||
// ReSharper disable once DelegateSubtraction | |||
Undo.undoRedoPerformed -= OnUndoRedo; //Remove old handler if present to prevent duplicates, doesn't cause errors if not present | |||
Undo.undoRedoPerformed += OnUndoRedo; | |||
} | |||
private void OnUndoRedo() | |||
{ | |||
Repaint(); | |||
} | |||
private bool _drawGraph; | |||
private string _displayText = ""; | |||
private void OnGUILogic() | |||
{ | |||
_drawGraph = true; | |||
if(Selection.gameObjects.Count(g => g.GetComponent<UdonBehaviour>()) > 1) | |||
{ | |||
_displayText = "Multi-object editing not supported"; | |||
_drawGraph = false; | |||
} | |||
else if(Selection.objects.Count(o => o != null && o is IUdonGraphDataProvider) > 1) // == typeof(UdonGraphProgramAsset) | |||
{ | |||
_displayText = "Multi-object editing not supported"; | |||
_drawGraph = false; | |||
} | |||
IUdonGraphDataProvider udonGraphProgramAsset = (IUdonGraphDataProvider)Selection.objects.FirstOrDefault(g => g != null && g is IUdonGraphDataProvider); | |||
if(udonGraphProgramAsset == null) | |||
{ | |||
GameObject behaviourObject = Selection.gameObjects.FirstOrDefault(g => g.GetComponent<UdonBehaviour>()); | |||
if(behaviourObject != null) | |||
{ | |||
UdonBehaviour udonBehaviour; | |||
var udonBehaviours = behaviourObject.GetComponents<UdonBehaviour>(); | |||
if (udonBehaviours.Length == 1 || lastClickedProgramSource == null) | |||
{ | |||
udonBehaviour = udonBehaviours[0]; | |||
} | |||
else | |||
{ | |||
udonBehaviour = udonBehaviours.FirstOrDefault(u => u.programSource == lastClickedProgramSource); | |||
if(udonBehaviour == null) | |||
{ | |||
// the last clicked graph is not available on this object, reset it | |||
lastClickedProgramSource = null; | |||
udonBehaviour = udonBehaviours[0]; | |||
} | |||
} | |||
AbstractUdonProgramSource programSource = udonBehaviour.programSource; | |||
if(programSource is IUdonGraphDataProvider asUdonGraphProgramAsset) | |||
{ | |||
udonGraphProgramAsset = asUdonGraphProgramAsset; | |||
} | |||
} | |||
} | |||
if (graph == null) | |||
{ | |||
graph = CreateInstance<UdonGraph>(); | |||
} | |||
if(udonGraphProgramAsset != null) | |||
{ | |||
if (graphGUI == null) | |||
{ | |||
graphGUI = CreateInstance<UdonGraphGUI>(); | |||
graphGUI.graph = graph; | |||
} | |||
if (graph == null) | |||
{ | |||
graph = CreateInstance<UdonGraph>(); | |||
graphGUI.graph = graph; | |||
} | |||
if(graph.graphProgramAsset == udonGraphProgramAsset) | |||
{ | |||
if (graph.data != null) | |||
{ | |||
return; | |||
} | |||
} | |||
titleContent = new GUIContent($"Udon - {udonGraphProgramAsset}"); | |||
graph.data = new UdonGraphData(udonGraphProgramAsset.GetGraphData()); | |||
graph.graphProgramAsset = udonGraphProgramAsset; | |||
graph.Reload(); | |||
graphGUI.CenterGraph(); | |||
} | |||
else | |||
{ | |||
if(graph.graphProgramAsset != null) | |||
{ | |||
return; | |||
} | |||
_displayText = "Create an Udon Graph Asset to begin."; | |||
_drawGraph = false; | |||
} | |||
} | |||
public void OnGUI() | |||
{ | |||
OnGUILogic(); | |||
DrawToolbar(); | |||
if (!_drawGraph) | |||
{ | |||
DrawCenteredText(_displayText); | |||
} | |||
else | |||
{ | |||
DrawGraph(); | |||
} | |||
} | |||
private void DrawNodeSearchBox() | |||
{ | |||
UdonNodeSearchMenu.DrawWindow(graph, graphGUI); | |||
} | |||
private static void DrawCenteredText(string text) | |||
{ | |||
GUILayout.BeginHorizontal(); | |||
GUILayout.FlexibleSpace(); | |||
GUILayout.Box("", _udonLogo); | |||
GUILayout.FlexibleSpace(); | |||
GUILayout.EndHorizontal(); | |||
GUILayout.Space(100); | |||
GUILayout.BeginHorizontal(); | |||
GUILayout.FlexibleSpace(); | |||
GUILayout.Label(text); | |||
GUILayout.FlexibleSpace(); | |||
GUILayout.EndHorizontal(); | |||
GUILayout.FlexibleSpace(); | |||
} | |||
private void DrawGraph() | |||
{ | |||
GUI.SetNextControlName("Default"); | |||
graphGUI.BeginGraphGUI(this, new Rect(0, TOOLBAR_HEIGHT, position.width, position.height)); | |||
graphGUI.OnGraphGUI(); | |||
graphGUI.EndGraphGUI(); | |||
} | |||
private void DrawToolbar() | |||
{ | |||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar); | |||
GUILayout.FlexibleSpace(); | |||
if (_drawGraph) | |||
{ | |||
using(new EditorGUI.DisabledScope(Application.isPlaying)) | |||
{ | |||
if (graph.graphProgramAsset is AbstractUdonProgramSource udonProgramSource) | |||
{ | |||
bool triggerRefresh; | |||
if (UdonEditorManager.Instance.IsProgramSourceRefreshQueued(udonProgramSource)) | |||
{ | |||
triggerRefresh = | |||
GUILayout.Button( | |||
$"Auto Compile in {UdonEditorManager.Instance.ProgramRefreshDelayRemaining:F0}s.", | |||
EditorStyles.toolbarButton); | |||
Repaint(); | |||
} | |||
else | |||
{ | |||
triggerRefresh = GUILayout.Button("Manual Compile", EditorStyles.toolbarButton); | |||
} | |||
if (triggerRefresh) | |||
{ | |||
UdonEditorManager.Instance.RefreshQueuedProgramSources(); | |||
} | |||
} | |||
DrawNodeSearchBox(); | |||
} | |||
if (GUILayout.Button(" Recenter ", EditorStyles.toolbarButton)) | |||
{ | |||
graphGUI.CenterGraph(); | |||
} | |||
using(new EditorGUI.DisabledScope(Application.isPlaying)) | |||
{ | |||
if(GUILayout.Button(" Reload Graph ", EditorStyles.toolbarButton)) | |||
{ | |||
graph.data = new UdonGraphData(graph.graphProgramAsset.GetGraphData()); //just do this in reload always | |||
graph.Reload(); | |||
graphGUI.CenterGraph(); | |||
} | |||
} | |||
} | |||
EditorGUILayout.EndHorizontal(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 8353c90dd1eff01458c55d0bd5290a1d | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,169 @@ | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using UnityEngine; | |||
using UnityEditor.Graphs; | |||
using UnityEngine.Serialization; | |||
using VRC.Udon.Graph; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
public class UdonNode : Node | |||
{ | |||
public string uid; | |||
public override void EndDrag() | |||
{ | |||
base.EndDrag(); | |||
((UdonGraph)graph).UpdateNodePosition(this); | |||
} | |||
public override void RemovingFromGraph() | |||
{ | |||
((UdonGraph)graph).DeleteNode(uid); | |||
foreach(Edge edge in inputEdges) | |||
{ | |||
graph.RemoveEdge(edge); | |||
} | |||
foreach(Edge edge in outputEdges) | |||
{ | |||
graph.RemoveEdge(edge); | |||
} | |||
base.RemovingFromGraph(); | |||
} | |||
private static readonly Dictionary<string, UdonNodeDefinition> NodeDefinitionCache = | |||
new Dictionary<string, UdonNodeDefinition>(); | |||
internal void PopulateEdges() | |||
{ | |||
UdonNodeData data = ((UdonGraph)graph).data.FindNode(uid); | |||
//UdonNodeDefinition nodeDefinition = ((UdonGraph)graph).data. | |||
UdonNodeDefinition udonNodeDefinition; | |||
if (NodeDefinitionCache.ContainsKey(data.fullName)) | |||
{ | |||
udonNodeDefinition = NodeDefinitionCache[data.fullName]; | |||
} | |||
else | |||
{ | |||
udonNodeDefinition = UdonEditorManager.Instance.GetNodeDefinition(data.fullName); | |||
NodeDefinitionCache.Add(data.fullName, udonNodeDefinition); | |||
} | |||
for (int i = 0; i < inputDataSlots.Count(); i++) // udonNodeDefinition.Inputs.Count | |||
{ | |||
if (data.nodeUIDs.Length <= i) | |||
{ | |||
continue; | |||
} | |||
if (string.IsNullOrEmpty(data.nodeUIDs[i])) | |||
{ | |||
continue; | |||
} | |||
string[] splitUID = data.nodeUIDs[i].Split('|'); | |||
string nodeUID = splitUID[0]; | |||
int otherIndex = 0; | |||
if (splitUID.Length > 1) | |||
{ | |||
otherIndex = int.Parse(splitUID[1]); | |||
} | |||
if (string.IsNullOrEmpty(nodeUID)) | |||
{ | |||
continue; | |||
} | |||
Node connectedNode = graph.nodes.FirstOrDefault(n => ((UdonNode) n).uid == nodeUID); | |||
if (connectedNode == null) | |||
{ | |||
Debug.LogError("Failed to connect node " + nodeUID); | |||
data.nodeUIDs[i] = ""; | |||
((UdonGraph) graph).ReSerializeData(); | |||
continue; | |||
} | |||
List<Slot> slots = inputDataSlots.ToList(); | |||
if (slots.Count <= i) | |||
{ | |||
Debug.LogError($"Failed to find input data slot (index {i}) for node {uid} {data.fullName}"); | |||
continue; | |||
} | |||
Slot destSlot = slots[i]; | |||
if (destSlot == null) | |||
{ | |||
Debug.LogError("Failed to find input data slot for node " + uid); | |||
continue; | |||
} | |||
if (otherIndex < 0) | |||
{ | |||
otherIndex = 0; | |||
} | |||
if (connectedNode.outputDataSlots.Count() <= otherIndex) | |||
{ | |||
otherIndex = 0; | |||
} | |||
Slot sourceSlot = connectedNode.outputDataSlots.ToList()[otherIndex]; //.FirstOrDefault(); | |||
// | |||
// catch | |||
// { | |||
// Debug.LogError($"failed to connect node {uid} {data.fullName} to node {((UdonNode)connectedNode).uid} | otherindex is {otherIndex}"); | |||
// } | |||
if(sourceSlot == null) | |||
{ | |||
Debug.LogError("Failed to find output data slot for node " + nodeUID); | |||
continue; | |||
} | |||
graph.Connect(sourceSlot, destSlot); | |||
} | |||
for (int i = 0; i < data.flowUIDs.Length; i++) | |||
{ | |||
string nodeUID = data.flowUIDs[i]; | |||
if (string.IsNullOrEmpty(nodeUID)) | |||
{ | |||
continue; | |||
} | |||
Node connectedNode = graph.nodes.FirstOrDefault(n => ((UdonNode) n).uid == nodeUID); | |||
if (connectedNode == null) | |||
{ | |||
Debug.LogError("Failed to connect flow node " + nodeUID); | |||
continue; | |||
} | |||
if (uid == "4e2c7cdc-8134-4616-bc83-783dc495759f") | |||
{ | |||
int count = 0; | |||
foreach (Slot slot in outputFlowSlots) count++; | |||
Debug.Log(count); | |||
} | |||
Slot sourceSlot = outputFlowSlots.Count() > 1 ? outputFlowSlots.ToArray()[i] : outputFlowSlots.FirstOrDefault(); | |||
if (sourceSlot == null) | |||
{ | |||
Debug.LogError("Failed to find output flow slot for node " + uid); | |||
continue; | |||
} | |||
Slot destSlot = connectedNode.inputFlowSlots.FirstOrDefault(); | |||
if (destSlot == null) | |||
{ | |||
Debug.LogError("Failed to find input flow slot for node " + nodeUID); | |||
continue; | |||
} | |||
graph.Connect(sourceSlot, destSlot); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: c132ff7be49726341a7eace42312fa93 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,817 @@ | |||
using System; | |||
using System.Collections; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Reflection; | |||
using System.Threading; | |||
using UnityEditor; | |||
using UnityEngine; | |||
using VRC.Udon.EditorBindings; | |||
using VRC.Udon.EditorBindings.Interfaces; | |||
using VRC.Udon.Graph; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
public class UdonNodeSearchMenu : EditorWindow | |||
{ | |||
private static UdonNodeSearchMenu _instance; | |||
private string _searchString = string.Empty; | |||
private UdonGraph _graph; | |||
private UdonGraphGUI _graphGUI; | |||
private Styles _styles; | |||
private Vector2 _scrollPosition = Vector2.zero; | |||
private IEnumerable<UdonNodeDefinition> _filteredNodeDefinitions; | |||
private IEnumerable<UdonNodeDefinition> _unfilteredNodeDefinitions; | |||
private Dictionary<string, UdonNodeDefinition> _searchDefinitions; | |||
private long _lastTime; | |||
private float _animValue; | |||
private float _animGoal = 1; | |||
private bool _isAnimating; | |||
private string _currentActivePath = ""; | |||
private string _currentActiveNode = ""; | |||
private int _currentActiveIndex; | |||
private string _nextActivePath = ""; | |||
private string _nextActiveNode = ""; | |||
private int _nextActiveIndex; | |||
private const int LIST_BUTTON_HEIGHT = 20; | |||
private const float DROPDOWN_HEIGHT = 320f; | |||
private float _dropDownWidth; | |||
private Rect _dropDownRect; | |||
private static readonly NodeMenuLayer NodeMenu = new NodeMenuLayer(); | |||
private void Awake() | |||
{ | |||
//_unfilteredNodeDefinitions = _udonEditorInterface.GetNodeDefinitions(); | |||
Dictionary<string, UdonNodeDefinition> baseNodeDefinitions = new Dictionary<string, UdonNodeDefinition>(); | |||
foreach (UdonNodeDefinition nodeDefinition in UdonEditorManager.Instance.GetNodeDefinitions().OrderBy(s => PrettyFullName(s))) | |||
{ | |||
string baseIdentifier = nodeDefinition.fullName; | |||
string[] splitBaseIdentifier = baseIdentifier.Split(new[] {"__"}, StringSplitOptions.None); | |||
if (splitBaseIdentifier.Length >= 2) | |||
{ | |||
baseIdentifier = $"{splitBaseIdentifier[0]}__{splitBaseIdentifier[1]}"; | |||
} | |||
if (baseNodeDefinitions.ContainsKey(baseIdentifier)) | |||
{ | |||
continue; | |||
} | |||
baseNodeDefinitions.Add(baseIdentifier, nodeDefinition); | |||
} | |||
_unfilteredNodeDefinitions = baseNodeDefinitions.Values; | |||
_searchDefinitions = _unfilteredNodeDefinitions.ToDictionary(nodeDefinition => SanitizedSearchString(nodeDefinition.fullName)); | |||
_filteredNodeDefinitions = _unfilteredNodeDefinitions; | |||
string SanitizedSearchString(string s) | |||
{ | |||
s = s.FriendlyNameify() | |||
.ToLowerInvariant() | |||
.Replace(".__", "."); | |||
if (s.StartsWith("variable_")) | |||
{ | |||
s = s.Replace("variable_", ""); | |||
} | |||
s = s.Replace("_", " ") | |||
.ReplaceFirst("unityengine", ""); | |||
//.ReplaceFirst("system", ""); | |||
return s; | |||
} | |||
} | |||
public static void DrawWindow(UdonGraph graph, UdonGraphGUI graphGUI) | |||
{ | |||
if(_instance == null) | |||
{ | |||
_instance = CreateInstance<UdonNodeSearchMenu>(); | |||
} | |||
Rect rect = GUILayoutUtility.GetLastRect(); | |||
bool goodState = graphGUI.selection.Count == 0; | |||
if(goodState) | |||
{ | |||
goodState = GUI.GetNameOfFocusedControl() != "NodeField"; | |||
} | |||
if(goodState && KeyUpEvent(KeyCode.Space) && !Event.current.shift) | |||
{ | |||
GUI.UnfocusWindow(); | |||
} | |||
if(!GUILayout.Button("Add Node", EditorStyles.toolbarButton, GUILayout.Width(120)) && !(KeyUpEvent(KeyCode.Space) && goodState)) | |||
{ | |||
return; | |||
} | |||
rect = RemapRectForPopup(rect); | |||
_instance.InitWindow(graph, graphGUI, rect); | |||
_instance.Repaint(); | |||
} | |||
private void InitWindow(UdonGraph graph, UdonGraphGUI graphGUI, Rect rect) | |||
{ | |||
_dropDownRect = rect; | |||
_graph = graph; | |||
_graphGUI = graphGUI; | |||
_styles = new Styles(); | |||
wantsMouseMove = true; | |||
if (NodeMenu.MenuName == null) | |||
{ | |||
new Thread(() => | |||
{ | |||
Thread.CurrentThread.IsBackground = true; | |||
NodeMenu.PopulateNodeMenu(); | |||
}).Start(); | |||
} | |||
_dropDownWidth = rect.width; | |||
ShowAsDropDown(rect, new Vector2(rect.width, DROPDOWN_HEIGHT)); | |||
Focus(); | |||
} | |||
private void OnGUI() | |||
{ | |||
GUI.Label(new Rect(0.0f, 0.0f, position.width, position.height), GUIContent.none, _styles.Background); | |||
if(_filteredNodeDefinitions == null) | |||
{ | |||
_filteredNodeDefinitions = _unfilteredNodeDefinitions; | |||
} | |||
DrawSearchBox(); | |||
if(_isAnimating && Event.current.type == EventType.Repaint) | |||
{ | |||
long ms = DateTime.Now.Millisecond; | |||
float maxDelta = -(ms - _lastTime) * .01f; | |||
_lastTime = ms; | |||
_animValue = Mathf.MoveTowards(_animValue, _animGoal, maxDelta); | |||
Repaint(); | |||
} | |||
if ((_animValue >= 1 || _animValue <= -1) && Event.current.type != EventType.Repaint) | |||
{ | |||
_isAnimating = false; | |||
_animValue = 0; | |||
_currentActivePath = _nextActivePath; | |||
_currentActiveNode = _nextActiveNode; | |||
_currentActiveIndex = _nextActiveIndex; | |||
_nextActivePath = ""; | |||
_nextActiveNode = ""; | |||
_nextActiveIndex = 0; | |||
while (_currentActiveIndex * LIST_BUTTON_HEIGHT >= | |||
_scrollPosition.y + DROPDOWN_HEIGHT - (LIST_BUTTON_HEIGHT * 3) - 5) | |||
{ | |||
_scrollPosition.y += LIST_BUTTON_HEIGHT; | |||
} | |||
while (_currentActiveIndex * LIST_BUTTON_HEIGHT < _scrollPosition.y) | |||
{ | |||
_scrollPosition.y -= LIST_BUTTON_HEIGHT; | |||
} | |||
} | |||
DrawListGUI(false, _animValue); | |||
if (_isAnimating) | |||
{ | |||
//if(animGoal == 1) | |||
DrawListGUI( true, _animValue - 1); | |||
//else | |||
DrawListGUI( true, _animValue + 1); | |||
} | |||
if(KeyUpEvent(KeyCode.Escape)) | |||
{ | |||
Close(); | |||
} | |||
} | |||
private void DrawSearchBox() | |||
{ | |||
EditorGUILayout.Space(); | |||
EditorGUILayout.BeginHorizontal(); | |||
GUI.SetNextControlName("nodeSearch"); | |||
EditorGUI.BeginChangeCheck(); | |||
_searchString = EditorGUILayout.TextField(_searchString, _styles.SearchTextField); | |||
if(EditorGUI.EndChangeCheck()) | |||
{ | |||
ApplySearchFilter(); | |||
} | |||
EditorGUI.FocusTextInControl("nodeSearch"); | |||
GUIStyle searchButtonStyle = | |||
_searchString == string.Empty ? _styles.SearchCancelButtonEmpty : _styles.SearchCancelButton; | |||
if(GUILayout.Button(string.Empty, searchButtonStyle)) | |||
{ | |||
_searchString = string.Empty; | |||
GUI.FocusControl(null); | |||
} | |||
EditorGUILayout.EndHorizontal(); | |||
EditorGUILayout.Space(); | |||
} | |||
private void ApplySearchFilter() | |||
{ | |||
string lowerSearchString = _searchString.ToLowerInvariant(); | |||
string unSpacedSearchString = lowerSearchString.Replace(" ", string.Empty); | |||
string[] lowerSearchStringArray = lowerSearchString.Split(' '); | |||
if(unSpacedSearchString.Length < 3) | |||
{ | |||
_filteredNodeDefinitions = new List<UdonNodeDefinition>(); | |||
return; | |||
} | |||
_filteredNodeDefinitions = _searchDefinitions | |||
.Where(s => s.Key.Contains(lowerSearchString)) | |||
.OrderBy(s => s.Key, new SearchComparer(lowerSearchString)) | |||
.Concat(_searchDefinitions | |||
.Where(s => s.Key.Contains(unSpacedSearchString)) | |||
.OrderBy(s => s.Key, new SearchComparer(unSpacedSearchString)) | |||
.Concat(_searchDefinitions | |||
.Where(s => lowerSearchStringArray.All(w => s.Key.Contains(w.ToLowerInvariant()))) | |||
.OrderBy(s => s.Key, new SearchComparer(lowerSearchStringArray.First().Replace(" ", string.Empty))) | |||
.Where(s => s.Key.IndexOf(lowerSearchStringArray.First().Replace(" ", string.Empty), StringComparison.InvariantCultureIgnoreCase) != -1) | |||
) | |||
) | |||
.Select(d => d.Value).Distinct().Take(200).ToArray(); | |||
//if(_filteredNodeDefinitions.Count() > 1000) | |||
//{ | |||
// _filteredNodeDefinitions = _filteredNodeDefinitions.Take(1000).ToArray(); | |||
//} | |||
} | |||
public static string PrettyFullName(UdonNodeDefinition nodeDefinition, bool keepLong = false) | |||
{ | |||
string fullName = nodeDefinition.fullName; | |||
string result; | |||
if(keepLong) | |||
{ | |||
result = fullName.Replace("UnityEngine", "UnityEngine.").Replace("System", "System."); | |||
} | |||
else | |||
{ | |||
result = fullName.Replace("UnityEngine", "").Replace("System", ""); | |||
} | |||
string[] resultSplit = result.Split(new[] {"__"}, StringSplitOptions.None); | |||
if(resultSplit.Length >= 3) | |||
{ | |||
string outName = ""; | |||
if (nodeDefinition.type != typeof(void)) | |||
{ | |||
if (nodeDefinition.Outputs.Count > 0) | |||
{ | |||
outName = string.Join(", ", nodeDefinition.Outputs.Select(o => o.name)); | |||
} | |||
} | |||
result = nodeDefinition.Inputs.Count > 0 | |||
? $"{resultSplit[0]}{resultSplit[1]}({string.Join(", ", nodeDefinition.Inputs.Select(s => s.name))}{outName})" | |||
: $"{resultSplit[0]}{resultSplit[1]}({resultSplit[2].Replace("_", ", ")}{outName})"; | |||
} | |||
else if(resultSplit.Length >= 2) | |||
{ | |||
result = $"{resultSplit[0]}{resultSplit[1]}()"; | |||
} | |||
if(!keepLong) | |||
{ | |||
result = result.FriendlyNameify(); | |||
result = result.Replace("op_", ""); | |||
result = result.Replace("_", " "); | |||
} | |||
return result; | |||
} | |||
//TODO: oof ouch my reflection | |||
private static object GetInstanceField(Type type, object instance, string fieldName) | |||
{ | |||
BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | |||
| BindingFlags.Static; | |||
FieldInfo field = type.GetField(fieldName, bindFlags); | |||
if (field != null) return field.GetValue(instance); | |||
throw new NullReferenceException($"Failed to get private field named: {fieldName} on object: {instance} of type: {type}"); | |||
} | |||
private Rect _headerRect; | |||
private void DrawListGUI( bool isNext = false, float anim = 0) | |||
{ | |||
Rect rect = position; | |||
rect.x = +1f; | |||
rect.x -= 230*anim; | |||
rect.y = 30f; | |||
rect.height -= 30f; | |||
rect.width -= 2f; | |||
GUILayout.BeginArea(rect); | |||
rect = GUILayoutUtility.GetRect(10f, 25f); | |||
if (string.IsNullOrEmpty(_currentActivePath) || _currentActivePath == "/") | |||
GUI.Label(rect, _searchString == string.Empty ? "Nodes" : "Search", _styles.Header); | |||
else | |||
{ | |||
string nestedTitle = _currentActivePath.Substring(0, _currentActivePath.Length - 1); | |||
if (GUI.Button(rect, _searchString == string.Empty ? nestedTitle : "Search", _styles.Header)) | |||
{ | |||
if (!isNext && !string.IsNullOrEmpty(_currentActivePath)) | |||
{ | |||
List<string> tmp = _currentActivePath.Split('/').ToList(); | |||
if (tmp.Count - 2 >= 0) | |||
{ | |||
_nextActiveNode = tmp[tmp.Count - 2]; | |||
_nextActiveIndex = 0; | |||
tmp.RemoveAt(_currentActivePath.Split('/').Length - 2); | |||
} | |||
_nextActivePath = string.Join("/", tmp.ToArray()); | |||
_isAnimating = true; | |||
_animGoal = 1; | |||
_lastTime = DateTime.Now.Millisecond; | |||
} | |||
} | |||
if (string.IsNullOrEmpty(_searchString)) | |||
GUI.Label(new Rect((float)(rect.x + 6.0), rect.y + 6f, 13f, 13f), "", _styles.LeftArrow); | |||
} | |||
_headerRect = rect; | |||
//TODO: don't be lazy, figure out the math for this so you don't have to loop :P | |||
bool repaint = false; | |||
while (_currentActiveIndex * LIST_BUTTON_HEIGHT >= | |||
_scrollPosition.y + DROPDOWN_HEIGHT - (LIST_BUTTON_HEIGHT * 3) - 5) | |||
{ | |||
_scrollPosition.y += LIST_BUTTON_HEIGHT; | |||
repaint = true; | |||
} | |||
while (_currentActiveIndex * LIST_BUTTON_HEIGHT < _scrollPosition.y) | |||
{ | |||
_scrollPosition.y -= LIST_BUTTON_HEIGHT; | |||
repaint = true; | |||
} | |||
if (repaint) | |||
{ | |||
Repaint(); | |||
} | |||
float oldScrollPosition = _scrollPosition.y; | |||
_scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition); | |||
if (string.IsNullOrEmpty(_searchString)) | |||
{ | |||
DrawNodeEntries(NodeMenu, isNext); | |||
} | |||
else | |||
{ | |||
for(int i = 0; i < (_filteredNodeDefinitions.Count() > 100 ? 100 : _filteredNodeDefinitions.Count()); i++) | |||
{ | |||
UdonNodeDefinition udonNodeDefinition = _filteredNodeDefinitions.ElementAt(i); | |||
string aB = _currentActiveNode; | |||
if(_filteredNodeDefinitions.All(f => f.fullName != aB)) | |||
{ | |||
_currentActiveNode = udonNodeDefinition.fullName; | |||
} | |||
GUIStyle buttonStyle = _currentActiveNode == udonNodeDefinition.fullName | |||
? _styles.ActiveComponentButton | |||
: _styles.ComponentButton; | |||
rect = GUILayoutUtility.GetRect(10f, LIST_BUTTON_HEIGHT); | |||
bool pressedButton = GUI.Button(rect, NodeContentWithIcon(udonNodeDefinition, PrettyFullName(udonNodeDefinition), PrettyFullName(udonNodeDefinition, true)), buttonStyle); | |||
if(pressedButton || | |||
(_currentActiveNode == udonNodeDefinition.fullName && (KeyUpEvent(KeyCode.Return) || KeyUpEvent(KeyCode.RightArrow)))) | |||
{ | |||
Rect graphExtents = (Rect)GetInstanceField(typeof(UdonGraph), _graph, "graphExtents"); | |||
graphExtents = new Rect(new Vector2(_graphGUI.scrollPosition.x + (graphExtents.x + (_graphGUI.Host.position.width / 2)) - 125, _graphGUI.scrollPosition.y + (graphExtents.y + (_graphGUI.Host.position.height / 2)) - 24), new Vector2(10, 10)); | |||
_graph.CreateNode(udonNodeDefinition, graphExtents.position); | |||
Close(); | |||
} | |||
if (rect.Contains(Event.current.mousePosition) && | |||
Event.current.mousePosition.y <= _dropDownRect.yMin + DROPDOWN_HEIGHT + _scrollPosition.y && | |||
Event.current.mousePosition.y > _headerRect.yMax + _scrollPosition.y - LIST_BUTTON_HEIGHT) | |||
{ | |||
_currentActiveNode = udonNodeDefinition.fullName; | |||
_currentActiveIndex = i; | |||
} | |||
if(Event.current.type != EventType.MouseMove) | |||
{ | |||
continue; | |||
} | |||
if(mouseOverWindow != null) | |||
{ | |||
mouseOverWindow.Repaint(); | |||
} | |||
} | |||
} | |||
GUILayout.EndScrollView(); | |||
if ((int)_scrollPosition.y != (int)oldScrollPosition) | |||
{ | |||
{ | |||
Repaint(); | |||
} | |||
} | |||
GUILayout.EndArea(); | |||
if (!string.IsNullOrEmpty(_searchString)) | |||
{ | |||
if (KeyUsedEvent(KeyCode.DownArrow)) | |||
{ | |||
OffsetActiveButton(1); | |||
} | |||
else if (KeyUsedEvent(KeyCode.UpArrow)) | |||
{ | |||
OffsetActiveButton(-1); | |||
} | |||
} | |||
} | |||
private string _loadingText = "Loading Nodes"; | |||
private void DrawNodeEntries(NodeMenuLayer layer, bool isNext) | |||
{ | |||
if (NodeMenu.MenuName == null) | |||
{ | |||
Rect buttonRect = GUILayoutUtility.GetRect(10f, LIST_BUTTON_HEIGHT, GUILayout.ExpandWidth(true)); | |||
GUI.Label(buttonRect, _loadingText, _styles.ComponentButton); | |||
_loadingText = $"{_loadingText}."; | |||
if (_loadingText.Contains("......")) | |||
{ | |||
_loadingText = "Loading Nodes"; | |||
} | |||
Repaint(); | |||
return; | |||
} | |||
NodeMenuLayer foundLayer = layer.FindLayer(_currentActivePath); | |||
string aN = _currentActiveNode; | |||
if (foundLayer.SubNodes.Count == 0) | |||
{ | |||
return; | |||
} | |||
if (foundLayer.SubNodes.All(n => n.MenuName != aN)) | |||
{ | |||
if (!isNext) | |||
{ | |||
_currentActiveNode = foundLayer.SubNodes.First().MenuName; | |||
_currentActiveIndex = 0; | |||
_scrollPosition.y = 0; | |||
aN = _currentActiveNode; | |||
} | |||
} | |||
if (Event.current.type == EventType.Used && Event.current.keyCode == KeyCode.DownArrow) | |||
{ | |||
int idx = foundLayer.SubNodes.Select((value, index) => new { value, index }).Where(pair => pair.value.MenuName == aN).Select(pair => pair.index).FirstOrDefault(); | |||
if (idx + 1 < foundLayer.SubNodes.Count) | |||
{ | |||
if (!isNext) | |||
{ | |||
_currentActiveNode = foundLayer.SubNodes[idx + 1].MenuName; | |||
_currentActiveIndex = idx + 1; | |||
if (_currentActiveIndex * LIST_BUTTON_HEIGHT >= | |||
_scrollPosition.y + DROPDOWN_HEIGHT - (LIST_BUTTON_HEIGHT * 3) - 5) | |||
{ | |||
_scrollPosition.y += LIST_BUTTON_HEIGHT; | |||
} | |||
} | |||
} | |||
} | |||
if (Event.current.type == EventType.Used && Event.current.keyCode == KeyCode.UpArrow) | |||
{ | |||
int idx = foundLayer.SubNodes.Select((value, index) => new { value, index }).Where(pair => pair.value.MenuName == aN).Select(pair => pair.index).FirstOrDefault(); | |||
if (idx - 1 >= 0) | |||
{ | |||
if (!isNext) | |||
{ | |||
_currentActiveNode = foundLayer.SubNodes[idx - 1].MenuName; | |||
_currentActiveIndex = idx - 1; | |||
if (_currentActiveIndex * LIST_BUTTON_HEIGHT < _scrollPosition.y) | |||
{ | |||
_scrollPosition.y -= LIST_BUTTON_HEIGHT; | |||
} | |||
} | |||
} | |||
} | |||
if (Event.current.type == EventType.Used && (Event.current.keyCode == KeyCode.Return || Event.current.keyCode == KeyCode.RightArrow)) | |||
{ | |||
if (!isNext) | |||
{ | |||
if (foundLayer.SubNodes.First(n => n.MenuName == aN).SubNodes.Count == 0) | |||
{ | |||
Rect graphExtents = (Rect)GetInstanceField(typeof(UdonGraph), _graph, "graphExtents"); | |||
graphExtents = new Rect(new Vector2(_graphGUI.scrollPosition.x + (graphExtents.x + (_graphGUI.Host.position.width / 2)) - 125, _graphGUI.scrollPosition.y + (graphExtents.y + (_graphGUI.Host.position.height / 2)) - 24), new Vector2(10, 10)); | |||
_graph.CreateNode(foundLayer.SubNodes.First(n => n.MenuName == aN).NodeDefinition, graphExtents.position); | |||
Close(); | |||
} | |||
else | |||
{ | |||
_nextActivePath = _currentActivePath + _currentActiveNode + "/"; | |||
_isAnimating = true; | |||
_animGoal = -1; | |||
_lastTime = DateTime.Now.Millisecond; | |||
} | |||
} | |||
} | |||
if (Event.current.type == EventType.Used && (Event.current.keyCode == KeyCode.Backspace || Event.current.keyCode == KeyCode.LeftArrow)) | |||
{ | |||
if (!isNext && !string.IsNullOrEmpty(_currentActivePath)) | |||
{ | |||
List<string> tmp = _currentActivePath.Split('/').ToList(); | |||
if (tmp.Count - 2 >= 0) | |||
{ | |||
_nextActiveNode = tmp[tmp.Count - 2]; | |||
_nextActiveIndex = 0; | |||
tmp.RemoveAt(_currentActivePath.Split('/').Length - 2); | |||
} | |||
_nextActivePath = string.Join("/", tmp.ToArray()); | |||
_isAnimating = true; | |||
_animGoal = 1; | |||
_lastTime = DateTime.Now.Millisecond; | |||
} | |||
} | |||
int nodeIndex = 0; | |||
foreach (NodeMenuLayer item in foundLayer.SubNodes) | |||
{ | |||
Rect buttonRect = GUILayoutUtility.GetRect(10f, LIST_BUTTON_HEIGHT, GUILayout.ExpandWidth(true)); | |||
if (!isNext) | |||
{ | |||
if (string.IsNullOrEmpty(_currentActiveNode)) | |||
{ | |||
_currentActiveNode = item.MenuName; | |||
_currentActiveIndex = nodeIndex; | |||
while (_currentActiveIndex * LIST_BUTTON_HEIGHT >= | |||
_scrollPosition.y + DROPDOWN_HEIGHT - (LIST_BUTTON_HEIGHT * 3) - 5) | |||
{ | |||
_scrollPosition.y += LIST_BUTTON_HEIGHT; | |||
} | |||
while (_currentActiveIndex * LIST_BUTTON_HEIGHT < _scrollPosition.y) | |||
{ | |||
_scrollPosition.y -= LIST_BUTTON_HEIGHT; | |||
} | |||
} | |||
} | |||
GUIStyle buttonStyle = _styles.ComponentButton; | |||
if (_currentActiveNode == item.MenuName) | |||
{ | |||
buttonStyle = _styles.ActiveComponentButton; | |||
} | |||
if (GUI.Button(buttonRect, | |||
NodeContentWithIcon(item.NodeDefinition, item.MenuName, | |||
item.NodeDefinition != null ? PrettyFullName(item.NodeDefinition, true) : item.MenuName), | |||
buttonStyle)) | |||
{ | |||
if (item.SubNodes.Count == 0) | |||
{ | |||
Rect graphExtents = (Rect)GetInstanceField(typeof(UdonGraph), _graph, "graphExtents"); | |||
graphExtents = new Rect(new Vector2(_graphGUI.scrollPosition.x + (graphExtents.x + (_graphGUI.Host.position.width / 2)) - 125, _graphGUI.scrollPosition.y + (graphExtents.y + (_graphGUI.Host.position.height / 2)) - 24), new Vector2(10, 10)); | |||
_graph.CreateNode(foundLayer.SubNodes.First(n => n.MenuName == aN).NodeDefinition, graphExtents.position); | |||
Close(); | |||
} | |||
if (!isNext) | |||
{ | |||
_nextActivePath = $"{_currentActivePath}{item.MenuName}/"; | |||
_isAnimating = true; | |||
_animGoal = -1; | |||
_lastTime = DateTime.Now.Millisecond; | |||
} | |||
} | |||
if (buttonRect.Contains(Event.current.mousePosition) && | |||
Event.current.mousePosition.y <= _dropDownRect.yMin + DROPDOWN_HEIGHT + _scrollPosition.y && | |||
Event.current.mousePosition.y > _headerRect.yMax + _scrollPosition.y - LIST_BUTTON_HEIGHT) | |||
{ | |||
if (!isNext) | |||
{ | |||
_currentActiveNode = item.MenuName; | |||
_currentActiveIndex = nodeIndex; | |||
while (_currentActiveIndex * LIST_BUTTON_HEIGHT >= | |||
_scrollPosition.y + DROPDOWN_HEIGHT - (LIST_BUTTON_HEIGHT * 3) - 5) | |||
{ | |||
_scrollPosition.y += LIST_BUTTON_HEIGHT; | |||
} | |||
while (_currentActiveIndex * LIST_BUTTON_HEIGHT < _scrollPosition.y) | |||
{ | |||
_scrollPosition.y -= LIST_BUTTON_HEIGHT; | |||
} | |||
} | |||
} | |||
if (buttonRect.Contains(Event.current.mousePosition)) | |||
{ | |||
if (!isNext) | |||
{ | |||
if (Event.current.type == EventType.MouseMove) | |||
{ | |||
if (mouseOverWindow != null) | |||
{ | |||
mouseOverWindow.Repaint(); | |||
} | |||
} | |||
} | |||
else | |||
{ | |||
_nextActiveNode = item.MenuName; | |||
_nextActiveIndex = nodeIndex; | |||
} | |||
} | |||
if (item.SubNodes.Count > 0) | |||
{ | |||
GUI.Label( | |||
new Rect((float) ((double) buttonRect.x + buttonRect.width - 13.0), buttonRect.y + 4f, 13f, | |||
13f), "", _styles.RightArrow); | |||
} | |||
nodeIndex++; | |||
} | |||
} | |||
private void OffsetActiveButton(int offset) | |||
{ | |||
_currentActiveIndex += offset; | |||
_currentActiveIndex = Mathf.Clamp(_currentActiveIndex, 0, _filteredNodeDefinitions.Count() - 1); | |||
_currentActiveNode = _filteredNodeDefinitions.ElementAt(_currentActiveIndex).fullName; | |||
while (_currentActiveIndex * LIST_BUTTON_HEIGHT >= | |||
_scrollPosition.y + DROPDOWN_HEIGHT - (LIST_BUTTON_HEIGHT * 3) - 5) | |||
{ | |||
_scrollPosition.y += LIST_BUTTON_HEIGHT; | |||
} | |||
while (_currentActiveIndex * LIST_BUTTON_HEIGHT < _scrollPosition.y) | |||
{ | |||
_scrollPosition.y -= LIST_BUTTON_HEIGHT; | |||
} | |||
} | |||
private static Rect RemapRectForPopup(Rect rect) | |||
{ | |||
rect.y += 26f; | |||
rect.x += rect.width; | |||
rect.width = 500; | |||
rect.x -= rect.width / 2; | |||
Vector2 v2 = GUIUtility.GUIToScreenPoint(new Vector2(rect.x, rect.y)); | |||
rect.x = v2.x; | |||
rect.y = v2.y; | |||
return rect; | |||
} | |||
private static bool KeyUpEvent(KeyCode keyCode) | |||
{ | |||
return Event.current.type == EventType.KeyUp && Event.current.keyCode == keyCode; | |||
} | |||
private static bool KeyUsedEvent(KeyCode keyCode) | |||
{ | |||
return Event.current.type == EventType.Used && Event.current.keyCode == keyCode; | |||
} | |||
private static Dictionary<Type, Texture2D> typeThumbCache = new Dictionary<Type, Texture2D>(); | |||
private GUIContent NodeContentWithIcon(UdonNodeDefinition nodeDefinition, string menuName, string tooltip) | |||
{ | |||
GUIContent content; | |||
if (nodeDefinition != null) | |||
{ | |||
Type thumbType = nodeDefinition.type; | |||
if (thumbType != null) | |||
{ | |||
if (thumbType.IsArray) | |||
{ | |||
thumbType = thumbType.GetElementType(); | |||
} | |||
} | |||
Texture2D thumb = GetCachedTypeThumbnail(thumbType); | |||
//TODO: This is real gross and hacky, figure out how to just let the name clip naturally without clipping the icon in the process | |||
int maxLength = (int)(_dropDownWidth / 7); | |||
menuName = menuName.Substring(0, menuName.Length <= maxLength ? menuName.Length : maxLength); | |||
while (GUI.skin.label.CalcSize(new GUIContent(menuName)).x > _dropDownWidth - 35) | |||
{ | |||
menuName = menuName.Substring(0, menuName.Length - 1); | |||
} | |||
content = thumb != null | |||
? new GUIContent($"{menuName}", thumb, tooltip) | |||
: new GUIContent($" {menuName}", tooltip); | |||
} | |||
else | |||
{ | |||
content = new GUIContent($" {menuName}", tooltip); | |||
} | |||
return content; | |||
} | |||
public static Texture2D GetCachedTypeThumbnail(Type thumbType) | |||
{ | |||
Texture2D thumb; | |||
if (thumbType == null) | |||
{ | |||
thumb = null; | |||
} | |||
else | |||
{ | |||
if (typeThumbCache.ContainsKey(thumbType)) | |||
{ | |||
thumb = typeThumbCache[thumbType]; | |||
} | |||
else | |||
{ | |||
thumb = AssetPreview.GetMiniTypeThumbnail(thumbType); | |||
typeThumbCache.Add(thumbType, thumb); | |||
} | |||
} | |||
return thumb; | |||
} | |||
private class Styles | |||
{ | |||
public readonly GUIStyle ComponentButton = new GUIStyle("PR Label"); | |||
public readonly GUIStyle ActiveComponentButton; | |||
public readonly GUIStyle Header = new GUIStyle("In BigTitle"); | |||
public readonly GUIStyle SearchTextField; | |||
public readonly GUIStyle SearchCancelButton; | |||
public readonly GUIStyle SearchCancelButtonEmpty; | |||
public readonly GUIStyle Background = "grey_border"; | |||
//public readonly GUIStyle PreviewBackground = "PopupCurveSwatchBackground"; | |||
//public readonly GUIStyle PreviewHeader = new GUIStyle(EditorStyles.label); | |||
//public readonly GUIStyle PreviewText = new GUIStyle(EditorStyles.wordWrappedLabel); | |||
public readonly GUIStyle RightArrow = "AC RightArrow"; | |||
public readonly GUIStyle LeftArrow = "AC LeftArrow"; | |||
//public readonly GUIStyle GroupButton; | |||
public Styles() | |||
{ | |||
ComponentButton.active = ComponentButton.onActive; | |||
ComponentButton.hover = ComponentButton.onHover; | |||
ComponentButton.padding.left -= 15; | |||
ComponentButton.fixedHeight = 20f; | |||
//ComponentButton.stretchWidth = true; | |||
ComponentButton.alignment = TextAnchor.MiddleLeft;//.MiddleLeft; | |||
ActiveComponentButton = new GUIStyle(ComponentButton); | |||
ActiveComponentButton.normal = ActiveComponentButton.onHover; | |||
Header.font = EditorStyles.boldLabel.font; | |||
SearchTextField = GUI.skin.FindStyle("SearchTextField"); | |||
SearchCancelButton = GUI.skin.FindStyle("SearchCancelButton"); | |||
SearchCancelButtonEmpty = GUI.skin.FindStyle("SearchCancelButtonEmpty"); | |||
Header.font = EditorStyles.boldLabel.font; | |||
//GroupButton = new GUIStyle(ComponentButton); | |||
//GroupButton.padding.left += 17; | |||
//PreviewText.padding.left += 3; | |||
//PreviewText.padding.right += 3; | |||
//++PreviewHeader.padding.left; | |||
//PreviewHeader.padding.right += 3; | |||
//PreviewHeader.padding.top += 3; | |||
//PreviewHeader.padding.bottom += 2; | |||
} | |||
} | |||
public class NodeMenuLayer | |||
{ | |||
public string MenuName; | |||
public UdonNodeDefinition NodeDefinition; | |||
public readonly List<NodeMenuLayer> SubNodes = new List<NodeMenuLayer>(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 9702ef19e1c2a0641a7c87ce25cfd1cf | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,19 @@ | |||
using UnityEngine; | |||
using VRC.Udon.Graph; | |||
using VRC.Udon.Graph.Interfaces; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
//[CreateAssetMenu(menuName = "VRChat/Udon/Udon Sub Graph Asset", fileName = "New Udon Sub Graph Asset")] | |||
public class UdonSubGraphAsset : ScriptableObject, IUdonGraphDataProvider | |||
{ | |||
[SerializeField] | |||
private UdonGraphData graphData = new UdonGraphData(); | |||
public UdonGraphData GetGraphData() | |||
{ | |||
return graphData; | |||
} | |||
} | |||
} | |||
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 0d0a5a452d53dea41823b928cf8e3961 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 22bbb058e8cdd1c4b83cb948209621d9 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 264ec3c8a1d423f42a144da0df6c5ebe | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,19 @@ | |||
using UnityEditor; | |||
namespace VRC.Udon.Editor.ProgramSources | |||
{ | |||
[CustomEditor(typeof(UdonProgramAsset))] | |||
public class UdonProgramAssetEditor : UnityEditor.Editor | |||
{ | |||
public override void OnInspectorGUI() | |||
{ | |||
bool dirty = false; | |||
UdonProgramAsset programAsset = (UdonProgramAsset)target; | |||
programAsset.RunEditorUpdate(null, ref dirty); | |||
if(dirty) | |||
{ | |||
EditorUtility.SetDirty(target); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 41d70977fa7936441afe41442f1862b2 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 0caf690532dfb974a9b4dfbac7889f51 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,96 @@ | |||
fileFormatVersion: 2 | |||
guid: 927841c571a405846b3442bc0aa56220 | |||
TextureImporter: | |||
fileIDToRecycleName: {} | |||
externalObjects: {} | |||
serializedVersion: 4 | |||
mipmaps: | |||
mipMapMode: 0 | |||
enableMipMap: 0 | |||
sRGBTexture: 0 | |||
linearTexture: 0 | |||
fadeOut: 0 | |||
borderMipMap: 0 | |||
mipMapsPreserveCoverage: 0 | |||
alphaTestReferenceValue: 0.5 | |||
mipMapFadeDistanceStart: 1 | |||
mipMapFadeDistanceEnd: 3 | |||
bumpmap: | |||
convertToNormalMap: 0 | |||
externalNormalMap: 0 | |||
heightScale: 0.25 | |||
normalMapFilter: 0 | |||
isReadable: 0 | |||
grayScaleToAlpha: 0 | |||
generateCubemap: 6 | |||
cubemapConvolution: 0 | |||
seamlessCubemap: 0 | |||
textureFormat: 1 | |||
maxTextureSize: 2048 | |||
textureSettings: | |||
serializedVersion: 2 | |||
filterMode: -1 | |||
aniso: 1 | |||
mipBias: -1 | |||
wrapU: 1 | |||
wrapV: 1 | |||
wrapW: -1 | |||
nPOTScale: 0 | |||
lightmap: 0 | |||
compressionQuality: 50 | |||
spriteMode: 0 | |||
spriteExtrude: 1 | |||
spriteMeshType: 1 | |||
alignment: 0 | |||
spritePivot: {x: 0.5, y: 0.5} | |||
spritePixelsToUnits: 100 | |||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||
spriteGenerateFallbackPhysicsShape: 1 | |||
alphaUsage: 1 | |||
alphaIsTransparency: 1 | |||
spriteTessellationDetail: -1 | |||
textureType: 2 | |||
textureShape: 1 | |||
maxTextureSizeSet: 0 | |||
compressionQualitySet: 0 | |||
textureFormatSet: 0 | |||
platformSettings: | |||
- buildTarget: DefaultTexturePlatform | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Standalone | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Android | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
spriteSheet: | |||
serializedVersion: 2 | |||
sprites: [] | |||
outline: [] | |||
physicsShape: [] | |||
spritePackingTag: | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,96 @@ | |||
fileFormatVersion: 2 | |||
guid: 3803fec4c7b065042891595e749524cc | |||
TextureImporter: | |||
fileIDToRecycleName: {} | |||
externalObjects: {} | |||
serializedVersion: 4 | |||
mipmaps: | |||
mipMapMode: 0 | |||
enableMipMap: 0 | |||
sRGBTexture: 0 | |||
linearTexture: 0 | |||
fadeOut: 0 | |||
borderMipMap: 0 | |||
mipMapsPreserveCoverage: 0 | |||
alphaTestReferenceValue: 0.5 | |||
mipMapFadeDistanceStart: 1 | |||
mipMapFadeDistanceEnd: 3 | |||
bumpmap: | |||
convertToNormalMap: 0 | |||
externalNormalMap: 0 | |||
heightScale: 0.25 | |||
normalMapFilter: 0 | |||
isReadable: 0 | |||
grayScaleToAlpha: 0 | |||
generateCubemap: 6 | |||
cubemapConvolution: 0 | |||
seamlessCubemap: 0 | |||
textureFormat: 1 | |||
maxTextureSize: 2048 | |||
textureSettings: | |||
serializedVersion: 2 | |||
filterMode: -1 | |||
aniso: 1 | |||
mipBias: -1 | |||
wrapU: 1 | |||
wrapV: 1 | |||
wrapW: -1 | |||
nPOTScale: 0 | |||
lightmap: 0 | |||
compressionQuality: 50 | |||
spriteMode: 0 | |||
spriteExtrude: 1 | |||
spriteMeshType: 1 | |||
alignment: 0 | |||
spritePivot: {x: 0.5, y: 0.5} | |||
spritePixelsToUnits: 100 | |||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||
spriteGenerateFallbackPhysicsShape: 1 | |||
alphaUsage: 1 | |||
alphaIsTransparency: 1 | |||
spriteTessellationDetail: -1 | |||
textureType: 2 | |||
textureShape: 1 | |||
maxTextureSizeSet: 0 | |||
compressionQualitySet: 0 | |||
textureFormatSet: 0 | |||
platformSettings: | |||
- buildTarget: DefaultTexturePlatform | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Standalone | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Android | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
spriteSheet: | |||
serializedVersion: 2 | |||
sprites: [] | |||
outline: [] | |||
physicsShape: [] | |||
spritePackingTag: | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,96 @@ | |||
fileFormatVersion: 2 | |||
guid: 7c75c00422f12124faed19bfb8dd96df | |||
TextureImporter: | |||
fileIDToRecycleName: {} | |||
externalObjects: {} | |||
serializedVersion: 4 | |||
mipmaps: | |||
mipMapMode: 0 | |||
enableMipMap: 0 | |||
sRGBTexture: 0 | |||
linearTexture: 0 | |||
fadeOut: 0 | |||
borderMipMap: 0 | |||
mipMapsPreserveCoverage: 0 | |||
alphaTestReferenceValue: 0.5 | |||
mipMapFadeDistanceStart: 1 | |||
mipMapFadeDistanceEnd: 3 | |||
bumpmap: | |||
convertToNormalMap: 0 | |||
externalNormalMap: 0 | |||
heightScale: 0.25 | |||
normalMapFilter: 0 | |||
isReadable: 0 | |||
grayScaleToAlpha: 0 | |||
generateCubemap: 6 | |||
cubemapConvolution: 0 | |||
seamlessCubemap: 0 | |||
textureFormat: 1 | |||
maxTextureSize: 2048 | |||
textureSettings: | |||
serializedVersion: 2 | |||
filterMode: -1 | |||
aniso: 1 | |||
mipBias: -1 | |||
wrapU: 1 | |||
wrapV: 1 | |||
wrapW: -1 | |||
nPOTScale: 0 | |||
lightmap: 0 | |||
compressionQuality: 50 | |||
spriteMode: 0 | |||
spriteExtrude: 1 | |||
spriteMeshType: 1 | |||
alignment: 0 | |||
spritePivot: {x: 0.5, y: 0.5} | |||
spritePixelsToUnits: 100 | |||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||
spriteGenerateFallbackPhysicsShape: 1 | |||
alphaUsage: 1 | |||
alphaIsTransparency: 1 | |||
spriteTessellationDetail: -1 | |||
textureType: 2 | |||
textureShape: 1 | |||
maxTextureSizeSet: 0 | |||
compressionQualitySet: 0 | |||
textureFormatSet: 0 | |||
platformSettings: | |||
- buildTarget: DefaultTexturePlatform | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Standalone | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Android | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
spriteSheet: | |||
serializedVersion: 2 | |||
sprites: [] | |||
outline: [] | |||
physicsShape: [] | |||
spritePackingTag: | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,96 @@ | |||
fileFormatVersion: 2 | |||
guid: 610088fc92e5fc64b8c7f9e9c51f2939 | |||
TextureImporter: | |||
fileIDToRecycleName: {} | |||
externalObjects: {} | |||
serializedVersion: 4 | |||
mipmaps: | |||
mipMapMode: 0 | |||
enableMipMap: 0 | |||
sRGBTexture: 0 | |||
linearTexture: 0 | |||
fadeOut: 0 | |||
borderMipMap: 0 | |||
mipMapsPreserveCoverage: 0 | |||
alphaTestReferenceValue: 0.5 | |||
mipMapFadeDistanceStart: 1 | |||
mipMapFadeDistanceEnd: 3 | |||
bumpmap: | |||
convertToNormalMap: 0 | |||
externalNormalMap: 0 | |||
heightScale: 0.25 | |||
normalMapFilter: 0 | |||
isReadable: 0 | |||
grayScaleToAlpha: 0 | |||
generateCubemap: 6 | |||
cubemapConvolution: 0 | |||
seamlessCubemap: 0 | |||
textureFormat: 1 | |||
maxTextureSize: 2048 | |||
textureSettings: | |||
serializedVersion: 2 | |||
filterMode: -1 | |||
aniso: 1 | |||
mipBias: -1 | |||
wrapU: 1 | |||
wrapV: 1 | |||
wrapW: -1 | |||
nPOTScale: 0 | |||
lightmap: 0 | |||
compressionQuality: 50 | |||
spriteMode: 0 | |||
spriteExtrude: 1 | |||
spriteMeshType: 1 | |||
alignment: 0 | |||
spritePivot: {x: 0.5, y: 0.5} | |||
spritePixelsToUnits: 100 | |||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||
spriteGenerateFallbackPhysicsShape: 1 | |||
alphaUsage: 1 | |||
alphaIsTransparency: 1 | |||
spriteTessellationDetail: -1 | |||
textureType: 2 | |||
textureShape: 1 | |||
maxTextureSizeSet: 0 | |||
compressionQualitySet: 0 | |||
textureFormatSet: 0 | |||
platformSettings: | |||
- buildTarget: DefaultTexturePlatform | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Standalone | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Android | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
spriteSheet: | |||
serializedVersion: 2 | |||
sprites: [] | |||
outline: [] | |||
physicsShape: [] | |||
spritePackingTag: | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,76 @@ | |||
fileFormatVersion: 2 | |||
guid: 0e2cfcbd717e75441b108d3ad9de2d29 | |||
TextureImporter: | |||
fileIDToRecycleName: {} | |||
externalObjects: {} | |||
serializedVersion: 4 | |||
mipmaps: | |||
mipMapMode: 0 | |||
enableMipMap: 1 | |||
sRGBTexture: 1 | |||
linearTexture: 0 | |||
fadeOut: 0 | |||
borderMipMap: 0 | |||
mipMapsPreserveCoverage: 0 | |||
alphaTestReferenceValue: 0.5 | |||
mipMapFadeDistanceStart: 1 | |||
mipMapFadeDistanceEnd: 3 | |||
bumpmap: | |||
convertToNormalMap: 0 | |||
externalNormalMap: 0 | |||
heightScale: 0.25 | |||
normalMapFilter: 0 | |||
isReadable: 0 | |||
grayScaleToAlpha: 0 | |||
generateCubemap: 6 | |||
cubemapConvolution: 0 | |||
seamlessCubemap: 0 | |||
textureFormat: 1 | |||
maxTextureSize: 2048 | |||
textureSettings: | |||
serializedVersion: 2 | |||
filterMode: -1 | |||
aniso: -1 | |||
mipBias: -1 | |||
wrapU: -1 | |||
wrapV: -1 | |||
wrapW: -1 | |||
nPOTScale: 1 | |||
lightmap: 0 | |||
compressionQuality: 50 | |||
spriteMode: 0 | |||
spriteExtrude: 1 | |||
spriteMeshType: 1 | |||
alignment: 0 | |||
spritePivot: {x: 0.5, y: 0.5} | |||
spritePixelsToUnits: 100 | |||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||
spriteGenerateFallbackPhysicsShape: 1 | |||
alphaUsage: 1 | |||
alphaIsTransparency: 0 | |||
spriteTessellationDetail: -1 | |||
textureType: 0 | |||
textureShape: 1 | |||
maxTextureSizeSet: 0 | |||
compressionQualitySet: 0 | |||
textureFormatSet: 0 | |||
platformSettings: | |||
- buildTarget: DefaultTexturePlatform | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
spriteSheet: | |||
serializedVersion: 2 | |||
sprites: [] | |||
outline: [] | |||
physicsShape: [] | |||
spritePackingTag: | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,121 @@ | |||
fileFormatVersion: 2 | |||
guid: 8cf68553c5a4bb140a6341072891aa88 | |||
TextureImporter: | |||
fileIDToRecycleName: {} | |||
externalObjects: {} | |||
serializedVersion: 9 | |||
mipmaps: | |||
mipMapMode: 0 | |||
enableMipMap: 0 | |||
sRGBTexture: 1 | |||
linearTexture: 0 | |||
fadeOut: 0 | |||
borderMipMap: 0 | |||
mipMapsPreserveCoverage: 0 | |||
alphaTestReferenceValue: 0.5 | |||
mipMapFadeDistanceStart: 1 | |||
mipMapFadeDistanceEnd: 3 | |||
bumpmap: | |||
convertToNormalMap: 0 | |||
externalNormalMap: 0 | |||
heightScale: 0.25 | |||
normalMapFilter: 0 | |||
isReadable: 0 | |||
streamingMipmaps: 0 | |||
streamingMipmapsPriority: 0 | |||
grayScaleToAlpha: 0 | |||
generateCubemap: 6 | |||
cubemapConvolution: 0 | |||
seamlessCubemap: 0 | |||
textureFormat: 1 | |||
maxTextureSize: 2048 | |||
textureSettings: | |||
serializedVersion: 2 | |||
filterMode: 0 | |||
aniso: 1 | |||
mipBias: -100 | |||
wrapU: 1 | |||
wrapV: 1 | |||
wrapW: -1 | |||
nPOTScale: 0 | |||
lightmap: 0 | |||
compressionQuality: 50 | |||
spriteMode: 0 | |||
spriteExtrude: 1 | |||
spriteMeshType: 1 | |||
alignment: 0 | |||
spritePivot: {x: 0.5, y: 0.5} | |||
spritePixelsToUnits: 100 | |||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||
spriteGenerateFallbackPhysicsShape: 1 | |||
alphaUsage: 1 | |||
alphaIsTransparency: 1 | |||
spriteTessellationDetail: -1 | |||
textureType: 2 | |||
textureShape: 1 | |||
singleChannelComponent: 0 | |||
maxTextureSizeSet: 0 | |||
compressionQualitySet: 0 | |||
textureFormatSet: 0 | |||
platformSettings: | |||
- serializedVersion: 2 | |||
buildTarget: DefaultTexturePlatform | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- serializedVersion: 2 | |||
buildTarget: Standalone | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- serializedVersion: 2 | |||
buildTarget: Android | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- serializedVersion: 2 | |||
buildTarget: WebGL | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
spriteSheet: | |||
serializedVersion: 2 | |||
sprites: [] | |||
outline: [] | |||
physicsShape: [] | |||
bones: [] | |||
spriteID: | |||
vertices: [] | |||
indices: | |||
edges: [] | |||
weights: [] | |||
spritePackingTag: | |||
pSDRemoveMatte: 0 | |||
pSDShowRemoveMatteOption: 0 | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,88 @@ | |||
fileFormatVersion: 2 | |||
guid: d0608d33a4043b2499adb1fee18f2a64 | |||
TextureImporter: | |||
fileIDToRecycleName: {} | |||
externalObjects: {} | |||
serializedVersion: 9 | |||
mipmaps: | |||
mipMapMode: 0 | |||
enableMipMap: 1 | |||
sRGBTexture: 1 | |||
linearTexture: 0 | |||
fadeOut: 0 | |||
borderMipMap: 0 | |||
mipMapsPreserveCoverage: 0 | |||
alphaTestReferenceValue: 0.5 | |||
mipMapFadeDistanceStart: 1 | |||
mipMapFadeDistanceEnd: 3 | |||
bumpmap: | |||
convertToNormalMap: 0 | |||
externalNormalMap: 0 | |||
heightScale: 0.25 | |||
normalMapFilter: 0 | |||
isReadable: 0 | |||
streamingMipmaps: 0 | |||
streamingMipmapsPriority: 0 | |||
grayScaleToAlpha: 0 | |||
generateCubemap: 6 | |||
cubemapConvolution: 0 | |||
seamlessCubemap: 0 | |||
textureFormat: 1 | |||
maxTextureSize: 2048 | |||
textureSettings: | |||
serializedVersion: 2 | |||
filterMode: -1 | |||
aniso: -1 | |||
mipBias: -100 | |||
wrapU: -1 | |||
wrapV: -1 | |||
wrapW: -1 | |||
nPOTScale: 1 | |||
lightmap: 0 | |||
compressionQuality: 50 | |||
spriteMode: 0 | |||
spriteExtrude: 1 | |||
spriteMeshType: 1 | |||
alignment: 0 | |||
spritePivot: {x: 0.5, y: 0.5} | |||
spritePixelsToUnits: 100 | |||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||
spriteGenerateFallbackPhysicsShape: 1 | |||
alphaUsage: 1 | |||
alphaIsTransparency: 0 | |||
spriteTessellationDetail: -1 | |||
textureType: 0 | |||
textureShape: 1 | |||
singleChannelComponent: 0 | |||
maxTextureSizeSet: 0 | |||
compressionQualitySet: 0 | |||
textureFormatSet: 0 | |||
platformSettings: | |||
- serializedVersion: 2 | |||
buildTarget: DefaultTexturePlatform | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
spriteSheet: | |||
serializedVersion: 2 | |||
sprites: [] | |||
outline: [] | |||
physicsShape: [] | |||
bones: [] | |||
spriteID: | |||
vertices: [] | |||
indices: | |||
edges: [] | |||
weights: [] | |||
spritePackingTag: | |||
pSDRemoveMatte: 0 | |||
pSDShowRemoveMatteOption: 0 | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,96 @@ | |||
fileFormatVersion: 2 | |||
guid: 17102758d03099542afc7a1808745eaf | |||
TextureImporter: | |||
fileIDToRecycleName: {} | |||
externalObjects: {} | |||
serializedVersion: 4 | |||
mipmaps: | |||
mipMapMode: 0 | |||
enableMipMap: 0 | |||
sRGBTexture: 0 | |||
linearTexture: 0 | |||
fadeOut: 0 | |||
borderMipMap: 0 | |||
mipMapsPreserveCoverage: 0 | |||
alphaTestReferenceValue: 0.5 | |||
mipMapFadeDistanceStart: 1 | |||
mipMapFadeDistanceEnd: 3 | |||
bumpmap: | |||
convertToNormalMap: 0 | |||
externalNormalMap: 0 | |||
heightScale: 0.25 | |||
normalMapFilter: 0 | |||
isReadable: 0 | |||
grayScaleToAlpha: 0 | |||
generateCubemap: 6 | |||
cubemapConvolution: 0 | |||
seamlessCubemap: 0 | |||
textureFormat: 1 | |||
maxTextureSize: 2048 | |||
textureSettings: | |||
serializedVersion: 2 | |||
filterMode: -1 | |||
aniso: 1 | |||
mipBias: -1 | |||
wrapU: 1 | |||
wrapV: 1 | |||
wrapW: -1 | |||
nPOTScale: 0 | |||
lightmap: 0 | |||
compressionQuality: 50 | |||
spriteMode: 0 | |||
spriteExtrude: 1 | |||
spriteMeshType: 1 | |||
alignment: 0 | |||
spritePivot: {x: 0.5, y: 0.5} | |||
spritePixelsToUnits: 100 | |||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||
spriteGenerateFallbackPhysicsShape: 1 | |||
alphaUsage: 1 | |||
alphaIsTransparency: 1 | |||
spriteTessellationDetail: -1 | |||
textureType: 2 | |||
textureShape: 1 | |||
maxTextureSizeSet: 0 | |||
compressionQualitySet: 0 | |||
textureFormatSet: 0 | |||
platformSettings: | |||
- buildTarget: DefaultTexturePlatform | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Standalone | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Android | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
spriteSheet: | |||
serializedVersion: 2 | |||
sprites: [] | |||
outline: [] | |||
physicsShape: [] | |||
spritePackingTag: | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,96 @@ | |||
fileFormatVersion: 2 | |||
guid: c0230adfeb2abe242b8d64c7e3bd2adc | |||
TextureImporter: | |||
fileIDToRecycleName: {} | |||
externalObjects: {} | |||
serializedVersion: 4 | |||
mipmaps: | |||
mipMapMode: 0 | |||
enableMipMap: 0 | |||
sRGBTexture: 0 | |||
linearTexture: 0 | |||
fadeOut: 0 | |||
borderMipMap: 0 | |||
mipMapsPreserveCoverage: 0 | |||
alphaTestReferenceValue: 0.5 | |||
mipMapFadeDistanceStart: 1 | |||
mipMapFadeDistanceEnd: 3 | |||
bumpmap: | |||
convertToNormalMap: 0 | |||
externalNormalMap: 0 | |||
heightScale: 0.25 | |||
normalMapFilter: 0 | |||
isReadable: 0 | |||
grayScaleToAlpha: 0 | |||
generateCubemap: 6 | |||
cubemapConvolution: 0 | |||
seamlessCubemap: 0 | |||
textureFormat: 1 | |||
maxTextureSize: 2048 | |||
textureSettings: | |||
serializedVersion: 2 | |||
filterMode: -1 | |||
aniso: -1 | |||
mipBias: -1 | |||
wrapU: 1 | |||
wrapV: 1 | |||
wrapW: -1 | |||
nPOTScale: 0 | |||
lightmap: 0 | |||
compressionQuality: 50 | |||
spriteMode: 1 | |||
spriteExtrude: 1 | |||
spriteMeshType: 1 | |||
alignment: 0 | |||
spritePivot: {x: 0.5, y: 0.5} | |||
spritePixelsToUnits: 100 | |||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||
spriteGenerateFallbackPhysicsShape: 1 | |||
alphaUsage: 1 | |||
alphaIsTransparency: 1 | |||
spriteTessellationDetail: -1 | |||
textureType: 2 | |||
textureShape: 1 | |||
maxTextureSizeSet: 0 | |||
compressionQualitySet: 0 | |||
textureFormatSet: 0 | |||
platformSettings: | |||
- buildTarget: DefaultTexturePlatform | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Standalone | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Android | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
spriteSheet: | |||
serializedVersion: 2 | |||
sprites: [] | |||
outline: [] | |||
physicsShape: [] | |||
spritePackingTag: | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,96 @@ | |||
fileFormatVersion: 2 | |||
guid: 8289cc16393cd3040a9920e71bfe10bc | |||
TextureImporter: | |||
fileIDToRecycleName: {} | |||
externalObjects: {} | |||
serializedVersion: 4 | |||
mipmaps: | |||
mipMapMode: 0 | |||
enableMipMap: 0 | |||
sRGBTexture: 0 | |||
linearTexture: 0 | |||
fadeOut: 0 | |||
borderMipMap: 0 | |||
mipMapsPreserveCoverage: 0 | |||
alphaTestReferenceValue: 0.5 | |||
mipMapFadeDistanceStart: 1 | |||
mipMapFadeDistanceEnd: 3 | |||
bumpmap: | |||
convertToNormalMap: 0 | |||
externalNormalMap: 0 | |||
heightScale: 0.25 | |||
normalMapFilter: 0 | |||
isReadable: 0 | |||
grayScaleToAlpha: 0 | |||
generateCubemap: 6 | |||
cubemapConvolution: 0 | |||
seamlessCubemap: 0 | |||
textureFormat: 1 | |||
maxTextureSize: 2048 | |||
textureSettings: | |||
serializedVersion: 2 | |||
filterMode: -1 | |||
aniso: 1 | |||
mipBias: -1 | |||
wrapU: 1 | |||
wrapV: 1 | |||
wrapW: -1 | |||
nPOTScale: 0 | |||
lightmap: 0 | |||
compressionQuality: 50 | |||
spriteMode: 0 | |||
spriteExtrude: 1 | |||
spriteMeshType: 1 | |||
alignment: 0 | |||
spritePivot: {x: 0.5, y: 0.5} | |||
spritePixelsToUnits: 100 | |||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} | |||
spriteGenerateFallbackPhysicsShape: 1 | |||
alphaUsage: 1 | |||
alphaIsTransparency: 1 | |||
spriteTessellationDetail: -1 | |||
textureType: 2 | |||
textureShape: 1 | |||
maxTextureSizeSet: 0 | |||
compressionQualitySet: 0 | |||
textureFormatSet: 0 | |||
platformSettings: | |||
- buildTarget: DefaultTexturePlatform | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Standalone | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
- buildTarget: Android | |||
maxTextureSize: 2048 | |||
resizeAlgorithm: 0 | |||
textureFormat: -1 | |||
textureCompression: 1 | |||
compressionQuality: 50 | |||
crunchedCompression: 0 | |||
allowsAlphaSplitting: 0 | |||
overridden: 0 | |||
androidETC2FallbackOverride: 0 | |||
spriteSheet: | |||
serializedVersion: 2 | |||
sprites: [] | |||
outline: [] | |||
physicsShape: [] | |||
spritePackingTag: | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |