Files
2025-06-09 23:23:13 +08:00

1157 lines
48 KiB
C#

using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
using static JBooth.MicroVerseCore.Browser.Package;
using Unity.Mathematics;
using JBooth.MicroVerseCore.Browser.CollectionWizard;
#if __MICROVERSE_SPLINES__
using UnityEngine.Splines;
#endif
namespace JBooth.MicroVerseCore.Browser
{
public class ContentBrowser : EditorWindow
{
static GUIContent COptionalVisibility = new GUIContent("O", "Optional content visibility");
static GUIContent CDescriptionVisibility = new GUIContent("D", "Description visibility");
static GUIContent CHelpVisibility = new GUIContent("?", "Help information visibility");
const string FilterOptionAllText = "All";
[MenuItem("Window/MicroVerse/Content Browser")]
public static void CreateWindow()
{
var w = EditorWindow.GetWindow<ContentBrowser>();
w.Show();
w.wantsMouseEnterLeaveWindow = true;
w.wantsMouseMove = true;
w.titleContent = new GUIContent("MicroVerse Browser");
}
public static List<T> LoadAllInstances<T>() where T : ScriptableObject
{
UnityEngine.Profiling.Profiler.BeginSample("Loading All browser content");
var ret = AssetDatabase.FindAssets($"t: {typeof(T).Name}").ToList()
.Select(AssetDatabase.GUIDToAssetPath)
.Select(AssetDatabase.LoadAssetAtPath<T>)
.ToList();
UnityEngine.Profiling.Profiler.EndSample();
return ret;
}
public enum Grouping
{
Package,
ContentType
}
/// <summary>
/// Subset of the Falloff filter type
/// </summary>
public enum FalloffDefault
{
Box = FalloffFilter.FilterType.Box,
Range = FalloffFilter.FilterType.Range,
}
public enum Tab
{
Height = ContentType.Height,
Texture = ContentType.Texture,
Vegetation = ContentType.Vegetation,
Objects = ContentType.Objects,
Audio = ContentType.Audio,
Biomes = ContentType.Biomes,
Roads = ContentType.Roads,
Caves = ContentType.Caves,
Global = ContentType.Global
}
public ContentTab[] contentTabs = new ContentTab[9]
{
new HeightTab(),
new TextureTab(),
new VegetationTab(),
new ObjectTab(),
new AmbienceTab(),
new BiomeTab(),
new RoadTab(),
new CaveTab(),
new GlobalTab()
};
GUIContent[] tabNames = new GUIContent[9] { new GUIContent("Height"), new GUIContent("Texture"), new GUIContent("Vegetation"), new GUIContent("Objects"), new GUIContent("Audio"), new GUIContent("Biomes"), new GUIContent("Roads"), new GUIContent("Caves"), new GUIContent("Global") };
public static Tab tab = Tab.Height;
List<Package> filteredCollectionPackages = null;
List<Package> filteredAdPackages = null;
Package selectedPackage;
List<BrowserContent> filteredCollections = null;
List<BrowserContent> filteredAds = null;
List<PresetItem> filteredPresets = null;
List<ContentCollection> allCollections = null;
List<BrowserContent> allContent = null;
private static int headerWidth = 180;
private static int listWidth = headerWidth + 10;
private Vector2 listScrollPosition = Vector2.zero;
private Color selectionColor = Color.green;
public FalloffDefault filterTypeDefault = FalloffDefault.Box;
public Vector3 heightStampDefaultScale = new Vector3(300, 120, 300);
public Vector3 textureStampDefaultScale = new Vector3(300, 120, 300);
public Vector3 vegetationStampDefaultScale = new Vector3(300, 120, 300);
public float roadHeightOffset = 0.0f;
/// <summary>
/// Selected item per tab, hash of browser content, since the object can change
/// </summary>
private Dictionary<Tab, Package> selectedTabItems = new Dictionary<Tab, Package>();
private bool optionalVisible = true;
private bool descriptionVisible = true;
private bool helpBoxVisible = true;
private ContentSelectionGrid contentSelectionGrid;
private ContentDragHandler contentDragHandler;
/// <summary>
/// Index of the Author popup
/// </summary>
private int selectedAuthorFilterIndex = 0;
/// <summary>
/// Search text of the selected author filter.
/// Don't use this string directly, rather use dedicated methods like <see cref="IsInFilter(BrowserContent)"/>.
/// </summary>
private string selectedAuthorFilterText = FilterOptionAllText;
private Grouping grouping = Grouping.Package;
/// <summary>
/// Get all presets that are currently visible in the browser
/// </summary>
/// <returns></returns>
public List<PresetItem> GetPresets()
{
if (selectedPackage == null)
return new();
List<PresetItem> presets = filteredPresets.Where(x => x.collection.packName == selectedPackage.packName).ToList();
return presets;
}
public Package GetSelectedPackage()
{
return selectedPackage;
}
public Tab GetSelectedTab()
{
return tab;
}
/// <summary>
/// Get the selected browser content if it can be uniquely identified.
/// Might not be the case if the same author creates multiple of the same content id and content type.
/// In case it can't be uniquely identified an error popup will show up and null will be returned.
/// </summary>
/// <returns>The selected content asset or null if no content is available or multiple assets were found</returns>
public BrowserContent GetSelectedBrowserContentAsset()
{
// at least package is required for detecting the current collection
if (grouping == Grouping.ContentType)
{
EditorUtility.DisplayDialog("Error", $"Not supported for grouping by {grouping}.\nAborting operation.", "Ok");
return null;
}
BrowserContent contentAsset = null;
int count = 0;
foreach (ContentCollection contentCollection in filteredCollections)
{
if (!IsInFilter(contentCollection))
continue;
if (contentCollection.id == selectedPackage.id && contentCollection.contentType == selectedPackage.contentType)
{
contentAsset = contentCollection;
count++;
}
}
if( count > 1)
{
EditorUtility.DisplayDialog("Error", $"Multiple content collections found: {count}\nConsider using a filter, eg Author filter.\nAborting operation.", "Ok");
return null;
}
return contentAsset;
}
/// <summary>
/// This returns the first ad for the currently selected package that's found or null if none was found.
/// </summary>
/// <returns></returns>
public ContentAd GetFirstSelectedAd()
{
if (selectedPackage == null || selectedPackage.packageType != PackageType.Ad)
return null;
foreach( BrowserContent content in allContent)
{
if (!(content is ContentAd))
continue;
if (content.id == selectedPackage.id && content.contentType == selectedPackage.contentType)
return content as ContentAd;
}
return null;
}
public List<BrowserContent> GetAllContent()
{
return allContent;
}
private void OnEnable()
{
// initialize settings; can't be initialized as global variable because this class is a scriptable object
optionalVisible = MicroVerseSettingsProvider.OptionalVisible;
descriptionVisible = MicroVerseSettingsProvider.DescriptionVisible;
helpBoxVisible = MicroVerseSettingsProvider.HelpVisible;
// content selection grid
if(contentSelectionGrid == null)
{
contentSelectionGrid = new ContentSelectionGrid(this);
}
contentSelectionGrid.OnEnable();
// content drag handler
if (contentDragHandler == null)
{
contentDragHandler = new ContentDragHandler(this);
}
contentDragHandler.OnEnable();
#if __MICROVERSE_SPLINES__
SceneView.beforeSceneGui -= OnSceneGUIEvent;
SceneView.beforeSceneGui += OnSceneGUIEvent;
SceneView.duringSceneGui -= OnSceneGUI;
SceneView.duringSceneGui += OnSceneGUI;
#endif
}
#if __MICROVERSE_SPLINES__
private void OnSceneGUI(SceneView view)
{
if (Event.current.type == EventType.Repaint && paintStroke != null && paintStroke.Count > 1)
{
Handles.color = Color.blue;
Handles.DrawAAPolyLine(20, paintStroke.ToArray());
if (paintStroke.Count > 2 && placementMode == PlacementMode.PaintArea)
{
Handles.DrawAAPolyLine(20, new Vector3[] { paintStroke[0], paintStroke[paintStroke.Count - 1] });
}
HandleUtility.Repaint();
}
}
#endif
#if __MICROVERSE_SPLINES__
List<float3> m_Stroke = new List<float3>(1024);
List<float3> m_Reduced = new List<float3>(512);
List<Vector3> paintStroke = new List<Vector3>(1024);
const int LeftMouseButton = 0;
static float splineStrokeDeltaThreshold = .1f;
static float splinePointReductionEpsilon = 5f;
static SplineContainer splineContainer;
// Tension affects how "curvy" splines are at knots. 0 is a sharp corner, 1 is maximum curvitude.
static float splineTension = 0.25f;
void RebuildSpline()
{
List<float3> samples = m_Stroke.Select(x => (float3)splineContainer.transform.InverseTransformPoint(x) + new float3(0, 0, 0)).ToList();
// Before setting spline knots, reduce the number of sample points.
SplineUtility.ReducePoints(samples, m_Reduced, splinePointReductionEpsilon);
var spline = splineContainer.Spline;
// Assign the reduced sample positions to the Spline knots collection. Here we are constructing new
// BezierKnots from a single position, disregarding tangent and rotation. The tangent and rotation will be
// calculated automatically in the next step wherein the tangent mode is set to "Auto Smooth."
spline.Knots = m_Reduced.Select(x => new BezierKnot(x));
var all = new SplineRange(0, spline.Count);
// Sets the tangent mode for all knots in the spline to "Auto Smooth."
spline.SetTangentMode(all, TangentMode.AutoSmooth);
// Sets the tension parameter for all knots. Note that the "Tension" parameter is only applicable to
// "Auto Smooth" mode knots.
spline.SetAutoSmoothTension(all, splineTension);
spline.Closed = placementMode == PlacementMode.PaintArea;
EditorUtility.SetDirty(splineContainer);
//m_Stats.text = $"Input Sample Count: {m_Stroke.Count}\nSpline Knot Count: {m_Reduced.Count}";
//Debug.Log($"Input Sample Count: {m_Stroke.Count}\nSpline Knot Count: {m_Reduced.Count}");
}
private void OnSceneGUIEvent(SceneView sceneView)
{
// perform method only when the mouse is really in the sceneview; the scene view would register other events as well
var isMouseInSceneView = new Rect(0, 0, sceneView.position.width, sceneView.position.height).Contains(Event.current.mousePosition);
if (!isMouseInSceneView)
{
return;
}
if (placementMode == PlacementMode.Place)
return;
if (MicroVerse.instance == null)
return;
if (Event.current.isMouse)
{
// left button = 0; right = 1; middle = 2
if (Event.current.button == LeftMouseButton)
{
// drag case
if (Event.current.type == EventType.MouseDown || Event.current.type == EventType.MouseDrag)
{
Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
// prefer terrain collisions..
var hits = Physics.RaycastAll(ray.origin, ray.direction, Mathf.Infinity);
if (hits.Length > 0)
{
RaycastHit hit = hits[0];
foreach (var h in hits)
{
if (h.collider.GetComponent<Terrain>())
{
hit = h;
break;
}
}
if (Event.current.type == EventType.MouseDown)
{
m_Stroke.Clear();
paintStroke.Clear();
string name = "Spline";
if (placementMode == PlacementMode.PaintArea)
{
name = "SplineArea";
}
else if (placementMode == PlacementMode.PaintSplinePath)
{
name = "SplinePath";
}
if (placementMode != PlacementMode.PaintSplinePath)
{
if (contentSelectionGrid.selectedPresetItem != null && contentSelectionGrid.selectedPresetItem.content != null && contentSelectionGrid.selectedPresetItem.content.prefab != null)
{
name += " - " + contentSelectionGrid.selectedPresetItem.content.prefab.name;
}
else if (contentSelectionGrid.selectedPresetItem != null && contentSelectionGrid.selectedPresetItem.content != null && contentSelectionGrid.selectedPresetItem.content.stamp != null)
{
var tex = AssetDatabase.LoadAssetAtPath<Texture2D>(AssetDatabase.GUIDToAssetPath(new GUID(contentSelectionGrid.selectedPresetItem.content.stamp)));
if (tex != null)
{
name += " - " + tex.name;
}
}
}
var go = new GameObject(name);
Undo.RegisterCreatedObjectUndo(go, "Content Browser Create Object");
Undo.SetTransformParent(go.transform, MicroVerse.instance.transform, "Content Browser Create Object");
splineContainer = Undo.AddComponent<SplineContainer>(go);
m_Stroke.Add(hit.point);
paintStroke.Add(hit.point);
}
else
{
Vector3 lastKnotWorldPosition = m_Stroke[m_Stroke.Count - 1];
float distance = Vector3.Distance(lastKnotWorldPosition, hit.point);
if (distance > splineStrokeDeltaThreshold)
{
m_Stroke.Add(hit.point);
paintStroke.Add(hit.point);
}
}
}
Event.current.Use();
}
if (Event.current.type == EventType.MouseUp && splineContainer != null)
{
RebuildSpline();
m_Stroke.Clear();
paintStroke.Clear();
if (placementMode == PlacementMode.PaintSplinePath)
{
var sp = Undo.AddComponent<SplinePath>(splineContainer.gameObject);
sp.width = 6;
sp.smoothness = 4;
}
else if (placementMode == PlacementMode.PaintArea ||
placementMode == PlacementMode.PaintSpline)
{
var area = Undo.AddComponent<SplineArea>(splineContainer.gameObject);
area.spline = splineContainer;
if (splineContainer.Spline != null && splineContainer.Spline.Count > 1)
{
bool canSpawn = true;
#if __MICROVERSE_SPLINES__
var collection = contentSelectionGrid.selectedPresetItem?.collection;
canSpawn = contentTabs[(int)tab].BeforeSplineSpawn(placementMode, splineContainer, collection, contentSelectionGrid.selectedPresetItem);
#endif
if (canSpawn && contentSelectionGrid.selectedPresetItem != null)
{
var spawned = contentTabs[(int)tab].Spawn(this, contentSelectionGrid.selectedPresetItem, false);
if (spawned != null)
{
Undo.RegisterCreatedObjectUndo(spawned, "Content Browser Create Object");
Undo.SetTransformParent(spawned.transform, splineContainer.transform, "Content Browser Create Object");
int areaUses = 0;
// move spawned object to center of spline area
Vector3 min = new Vector3(99999, 99999, 99999);
Vector3 max = new Vector3(-99999, -99999, -99999);
float3 avg = splineContainer.Spline[0].Position;
for (int i = 1; i < splineContainer.Spline.Count; ++i)
{
var p = splineContainer.Spline[i].Position;
avg += p;
min = Vector3.Min(min, p);
max = Vector3.Max(max, p);
}
avg /= splineContainer.Spline.Count;
spawned.transform.position = splineContainer.transform.localToWorldMatrix.MultiplyPoint(avg);
Vector3 range = max - min;
range.y = 120;
spawned.transform.localScale = range;
// now setup stamps
Stamp[] stamps = spawned.GetComponentsInChildren<Stamp>();
foreach (var s in stamps)
{
if (s.stampVersion == 0)
s.stampVersion = 1;
}
var over = spawned.GetComponent<FalloffOverride>();
if (over != null)
{
var falloff = over.filter;
if (falloff != null)
{
falloff.filterType = FalloffFilter.FilterType.SplineArea;
falloff.splineArea = area;
falloff.falloffRange = Vector2.one;
if (placementMode == PlacementMode.PaintSpline)
{
falloff.splineAreaFalloffBoost = 10;
falloff.splineAreaFalloff = 5;
}
areaUses++;
}
}
else
{
foreach (var s in stamps)
{
var falloffFilter = s.GetFilterSet();
FalloffFilter falloff = null;
if (falloffFilter != null)
{
falloff = falloffFilter.falloffFilter;
}
else
{
// height stamps don't have falloff sets
var hm = s as HeightStamp;
if (hm != null)
{
falloff = hm.falloff;
}
}
if (falloff != null)
{
falloff.filterType = FalloffFilter.FilterType.SplineArea;
falloff.splineArea = area;
falloff.falloffRange = Vector2.one;
if (placementMode == PlacementMode.PaintSpline)
{
falloff.splineAreaFalloffBoost = 10;
falloff.splineAreaFalloff = 5;
}
areaUses++;
}
}
}
contentTabs[(int)tab].AfterSplineSpawn(placementMode, spawned, splineContainer, area, areaUses);
}
}
}
}
// clear spline container too
splineContainer = null;
if (placementMode != PlacementMode.Place && Event.current.shift == false)
{
placementMode = PlacementMode.Place;
}
Repaint();
}
}
}
}
#endif
private void OnDisable()
{
// content selection grid
contentSelectionGrid.OnDisable();
contentSelectionGrid = null;
// content drag handler
contentDragHandler.OnDisable();
contentDragHandler = null;
#if __MICROVERSE_SPLINES__
SceneView.beforeSceneGui -= OnSceneGUIEvent;
SceneView.duringSceneGui -= OnSceneGUI;
#endif
}
bool HasContentForAd(ContentAd ad, List<ContentCollection> content)
{
for (int i = 0; i < content.Count; ++i)
{
if (content[i].id == ad.id && !string.IsNullOrEmpty(ad.id))
return true;
if (content[i].packName == ad.packName && !string.IsNullOrEmpty(ad.packName))
return true;
}
return false;
}
private void OnFocus()
{
allContent = LoadAllInstances<BrowserContent>();
}
public enum PlacementMode
{
Place = 0,
PaintSplinePath = 1,
PaintSpline = 2,
PaintArea = 3
}
Texture2D splineIcon;
Texture2D splinePathIcon;
Texture2D splineAreaIcon;
Texture2D placementIcon;
static int placementModeHeight = 24;
public static PlacementMode placementMode = PlacementMode.Place;
void DrawPlacementTools()
{
if (splineIcon == null)
{
string filename = EditorGUIUtility.isProSkin ? "d_microverse_spline_icon" : "microverse_spline_icon";
splineIcon = Resources.Load<Texture2D>(filename);
}
if (splinePathIcon == null)
{
string filename = EditorGUIUtility.isProSkin ? "d_microverse_splinepath_icon" : "microverse_splinepath_icon";
splinePathIcon = Resources.Load<Texture2D>(filename);
}
if (splineAreaIcon == null)
{
string filename = EditorGUIUtility.isProSkin ? "d_microverse_splinearea_icon" : "microverse_splinearea_icon";
splineAreaIcon = Resources.Load<Texture2D>(filename);
}
if (placementIcon == null)
{
string filename = EditorGUIUtility.isProSkin ? "d_microverse_placement_icon" : "microverse_placement_icon";
placementIcon = Resources.Load<Texture2D>(filename);
}
bool supportsPlacement = true;
bool supportsSpline = contentTabs[(int)tab].SupportsPlacementMode(PlacementMode.PaintSpline);
bool supportsSplineArea = contentTabs[(int)tab].SupportsPlacementMode(PlacementMode.PaintArea);
bool supportsSplinePath = contentTabs[(int)tab].SupportsPlacementMode(PlacementMode.PaintSplinePath);
GUIContent[] toolContent;
PlacementMode[] placementModes;
int count = (supportsPlacement ? 1 : 0) + (supportsSpline ? 1 : 0) + (supportsSplineArea ? 1 : 0) + (supportsSplinePath ? 1 : 0);
toolContent = new GUIContent[count];
placementModes = new PlacementMode[count];
toolContent[0] = new GUIContent("", placementIcon, "Drag and Drop into the scene");
placementModes[0] = PlacementMode.Place;
int index = 1;
if (supportsSpline)
{
toolContent[index] = new GUIContent("", splineIcon, "Use the selected content constrained to an open spline");
placementModes[index] = PlacementMode.PaintSpline;
index++;
}
if (supportsSplineArea)
{
toolContent[index] = new GUIContent("", splineAreaIcon, "Use the selected content constrained to a spline area");
placementModes[index] = PlacementMode.PaintArea;
index++;
}
if (supportsSplinePath)
{
toolContent[index] = new GUIContent("", splinePathIcon, "Create a spline path by painting");
placementModes[index] = PlacementMode.PaintSplinePath;
index++;
}
EditorGUILayout.BeginHorizontal(GUILayout.Width(196));
if (count > 1)
{
EditorGUILayout.LabelField("Placement:", GUILayout.Width(64), GUILayout.Height(placementModeHeight));
int placeIdx = 0;
for (int i = 0; i < placementModes.Length; ++i)
{
if (placementModes[i] == placementMode)
{
placeIdx = i;
}
}
placeIdx = GUILayout.Toolbar(placeIdx, toolContent, GUILayout.Width(count * 48), GUILayout.Height(placementModeHeight));
placementMode = placementModes[placeIdx];
}
#if __MICROVERSE_SPLINES__
if (placementMode != PlacementMode.Place)
{
splineStrokeDeltaThreshold = EditorGUILayout.Slider("Delta", splineStrokeDeltaThreshold, 0.1f, 0.4f);
splinePointReductionEpsilon = EditorGUILayout.Slider("Epsilon", splinePointReductionEpsilon, 1, 20f);
splineTension = EditorGUILayout.Slider("Tension", splineTension, 0.1f, 0.5f);
}
#endif
EditorGUILayout.EndHorizontal();
}
void DrawTabToolbar()
{
EditorGUILayout.BeginVertical("box");
{
float prev = EditorGUIUtility.labelWidth;
EditorGUIUtility.labelWidth = 80f;
{
EditorGUILayout.BeginHorizontal();
{
DrawPlacementTools();
contentTabs[(int)tab].DrawToolbar(this);
}
EditorGUILayout.EndHorizontal();
}
EditorGUIUtility.labelWidth = prev;
}
EditorGUILayout.EndVertical();
}
/// <summary>
/// Get popup options by getting a distinct list of all authors.
/// Only uses content collections, no Ads considers
/// </summary>
/// <returns></returns>
private string[] GetFilterOptions()
{
List<string> options = allContent
.Where( x => x is ContentCollection) // installed
.Select(x => x.author) // author name
.Distinct() // unique
.ToList();
options.Sort();
// add "All" on top of the list
options.Insert(0, FilterOptionAllText);
return options.ToArray();
}
/// <summary>
/// Determine whether the collection is in the filter or not.
/// </summary>
/// <param name="collection"></param>
/// <returns></returns>
private bool IsInFilter( BrowserContent collection)
{
if (collection != null && collection.renderPipeline != 0)
{
#if USING_HDRP
if (!collection.renderPipeline.HasFlag(BrowserContent.RenderPipeline.HDRP))
return false;
#elif USING_URP
if (!collection.renderPipeline.HasFlag(BrowserContent.RenderPipeline.URP))
return false;
#else
if (!collection.renderPipeline.HasFlag(BrowserContent.RenderPipeline.Standard))
return false;
#endif
}
// All
if (selectedAuthorFilterIndex == 0)
return true;
if (collection == null || collection.author == null)
return true;
return selectedAuthorFilterText.Equals( collection.author);
}
/// <summary>
/// Update the content to be displayed
/// </summary>
private void UpdateFilteredContent( Tab selectedTab)
{
if (allContent == null)
allContent = LoadAllInstances<BrowserContent>();
List<ContentAd> allAds = allContent
.Where(x => (int)x.contentType == (int)selectedTab)
.Where(x => x is ContentAd)
.Cast<ContentAd>()
.Where(x => IsInFilter(x))
.ToList();
allCollections = allContent
.Where(x => (int)x.contentType == (int)selectedTab)
.Where(x => x is ContentCollection)
.Cast<ContentCollection>()
.Where(x => IsInFilter(x))
.ToList();
// get all Ad ids which are invalid, ie require an object, but the object isn't set
List<string> invalidAdIds = allAds
.Where(x => x.requireInstalledObject && x.installedObject == null && x.id != null)
.Select(x => x.id)
.ToList();
// remove collections which have invalid Ad ids
allCollections = allCollections.Where(x => !invalidAdIds.Contains(x.id)).ToList();
// create filtered content
filteredCollections = new List<BrowserContent>();
filteredCollections.AddRange(allCollections);
// add ads
filteredAds = new List<BrowserContent>();
filteredAds.AddRange( allAds.Where(x => x.requireInstalledObject && x.installedObject == null || !HasContentForAd(x, allCollections)).ToList());
filteredCollections = filteredCollections.OrderByDescending(x => x.GetType().Name).ThenBy(x => x.packName).ToList();
// create draggable presets
// TODO: adjust filter; allow all in one tab
// TODO: group list by pack
filteredPresets = new List<PresetItem>();
foreach (ContentCollection contentCollection in filteredCollections)
{
// author filter
if (!IsInFilter(contentCollection))
continue;
ContentData[] contentData = contentCollection.contents;
if (contentData != null)
{
for (int i = 0; i < contentData.Length; i++)
{
bool isInFilter = true;
#if __MICROVERSE_ROADS__
// special consideration for roads in spline paint mode:
// show only presets which can be painted, ie which have a mapping intersection to road config
if( tab == Tab.Roads && placementMode == PlacementMode.PaintSpline)
{
if (((RoadSystemConfig)contentCollection.systemConfig) != null)
{
isInFilter = ((RoadSystemConfig)contentCollection.systemConfig).splinePaintList.Select(x => x.intersection).Contains(contentData[i].prefab);
}
}
#endif
if (isInFilter)
{
filteredPresets.Add(new PresetItem(contentCollection, contentData[i], i));
}
}
}
}
// packages
filteredCollectionPackages = new List<Package>();
foreach (ContentCollection c in filteredCollections)
{
Package package = new Package(c.id, c.packName, c.contentType, PackageType.Collection);
if (!filteredCollectionPackages.Contains(package))
{
filteredCollectionPackages.Add(package);
}
}
filteredAdPackages = new List<Package>();
foreach (ContentAd c in filteredAds)
{
Package package = new Package(c.id, c.packName, c.contentType, PackageType.Ad);
if (!filteredAdPackages.Contains(package))
{
filteredAdPackages.Add(package);
}
}
}
void DrawMissingModule(string name, string url)
{
EditorGUILayout.HelpBox("You do not have the " + name + " module installed, which is required for content on this tab", MessageType.Error);
if (GUILayout.Button("Get " + name, GUILayout.Width(200)))
{
Application.OpenURL(url);
}
}
void DrawMissingModule()
{
switch (tab)
{
case Tab.Audio:
{
#if !__MICROVERSE_AMBIANCE__
DrawMissingModule("Ambience", MicroVerseEditor.ambientUrl);
#endif
break;
}
case Tab.Vegetation:
{
#if !__MICROVERSE_VEGETATION__
DrawMissingModule("Vegetation", MicroVerseEditor.vegUrl);
#endif
break;
}
case Tab.Roads:
{
#if !__MICROVERSE_ROADS__
DrawMissingModule("Roads", MicroVerseEditor.roadUrl);
#endif
#if !__MICROVERSE_SPLINES__
DrawMissingModule("Spline", MicroVerseEditor.splineUrl);
#endif
break;
}
case Tab.Caves:
{
#if !__MICROVERSE_ROADS__
DrawMissingModule("Roads", MicroVerseEditor.roadUrl);
#endif
#if !__MICROVERSE_SPLINES__
DrawMissingModule("Spline", MicroVerseEditor.splineUrl);
#endif
break;
}
}
}
private void OnGUI()
{
Package oldPackage = null;
var oldTab = tab;
EditorGUILayout.BeginHorizontal();
{
// tab bar
tab = (Tab)GUILayout.Toolbar((int)tab, tabNames);
// optional button
if (GUILayout.Button(COptionalVisibility, EditorStyles.miniButton, GUILayout.Width(20)))
{
optionalVisible = !optionalVisible;
MicroVerseSettingsProvider.OptionalVisible = optionalVisible;
}
// description button
if (GUILayout.Button(CDescriptionVisibility, EditorStyles.miniButton, GUILayout.Width(20)))
{
descriptionVisible = !descriptionVisible;
MicroVerseSettingsProvider.DescriptionVisible = descriptionVisible;
}
// help button
if (GUILayout.Button(CHelpVisibility, EditorStyles.miniButton, GUILayout.Width(20)))
{
helpBoxVisible = !helpBoxVisible;
MicroVerseSettingsProvider.HelpVisible = helpBoxVisible;
}
}
EditorGUILayout.EndHorizontal();
// set filtered content list; this can be optimized by not calling it all the time in OnGUI()
// must happen after the tab got selected
// could be optimized to happen only on tab switch and on package change. but then we wouldn't auto-detect changes, so leaving it as it is for now
UpdateFilteredContent( tab);
if (tab != oldTab)
{
oldPackage = selectedTabItems.GetValueOrDefault(oldTab);
// try to keep the same package group selected during tab switch
foreach (Package package in filteredCollectionPackages)
{
if (package.IsInGroup(oldPackage))
{
selectedPackage = package;
selectedTabItems[tab] = selectedPackage;
break;
}
}
}
EditorGUILayout.BeginHorizontal();
{
EditorGUILayout.BeginVertical("box", GUILayout.Width(listWidth));
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Group By", EditorStyles.miniBoldLabel, GUILayout.Width(60));
grouping = (Grouping) EditorGUILayout.EnumPopup( grouping, GUILayout.Width(listWidth-80));
EditorGUILayout.EndHorizontal();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Author", EditorStyles.miniBoldLabel, GUILayout.Width(60));
string[] options = GetFilterOptions();
selectedAuthorFilterIndex = EditorGUILayout.Popup(selectedAuthorFilterIndex, options, GUILayout.Width(listWidth-80));
EditorGUILayout.EndHorizontal();
if (GUILayout.Button("Create New Collection"))
{
BrowserToolsEditorWindow.CreateWindow();
}
selectedAuthorFilterText = selectedAuthorFilterIndex < options.Length ? options[selectedAuthorFilterIndex] : "<Undefined>";
// nothing selected => pick first one if available
if (selectedPackage == null && filteredCollectionPackages.Count > 0)
{
selectedPackage = filteredCollectionPackages[0];
selectedTabItems[tab] = selectedPackage;
}
if (grouping == Grouping.Package)
{
DrawPackageList();
}
EditorGUILayout.EndVertical();
EditorGUILayout.BeginHorizontal();
{
EditorGUILayout.BeginVertical();
{
// falloff toolbar
DrawTabToolbar();
// content drag/drop
contentDragHandler.OnGUI();
// package content
if (grouping == Grouping.ContentType)
{
selectedPackage = null;
contentSelectionGrid.Draw(filteredPresets, grouping);
}
else if (grouping == Grouping.Package)
{
if (selectedPackage != null)
{
if (selectedPackage.packageType == PackageType.Collection)
{
List<PresetItem> draggablePresets = filteredPresets.Where(x => x.collection.packName == selectedPackage.packName).ToList();
contentSelectionGrid.Draw(draggablePresets, grouping);
}
else if (selectedPackage.packageType == PackageType.Ad)
{
ContentAd selectedAd = GetFirstSelectedAd();
if (selectedAd != null && !HasContentForAd(selectedAd, allCollections))
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.BeginVertical();
if (GUILayout.Button("Download", GUILayout.Width(420)))
{
var path = selectedAd.downloadPath;
if (path.Contains("assetstore.unity.com"))
{
path += "?aid=25047";
}
Application.OpenURL(path);
}
Rect r = EditorGUILayout.GetControlRect(GUILayout.MaxWidth(420), GUILayout.MaxHeight(280));
if (GUI.Button(r, ""))
{
var path = selectedAd.downloadPath;
if (path.Contains("assetstore.unity.com"))
{
path += "?aid=25047";
}
Application.OpenURL(path);
}
if (selectedAd.image == null)
{
GUI.DrawTexture(r, Texture2D.whiteTexture);
}
else
{
GUI.DrawTexture(r, selectedAd.image);
}
EditorGUILayout.EndVertical();
if (!string.IsNullOrEmpty(selectedAd.description))
{
GUIStyle style = new GUIStyle(EditorStyles.label);
style.wordWrap = true;
EditorGUILayout.LabelField(selectedAd.description, style);
}
EditorGUILayout.EndHorizontal();
}
}
}
}
}
EditorGUILayout.EndVertical();
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndHorizontal();
}
/// <summary>
/// Draw all packages. First Installed, then Optional
/// </summary>
private void DrawPackageList()
{
listScrollPosition = GUILayout.BeginScrollView(listScrollPosition, GUILayout.Width(listWidth+20));
{
bool hasCollections = filteredCollectionPackages.Count > 0;
bool hasAds = filteredAdPackages.Count > 0;
if(hasCollections)
{
EditorGUILayout.LabelField("Installed", EditorStyles.miniBoldLabel, GUILayout.Width(listWidth));
DrawPackageListItems(filteredCollectionPackages);
}
if (optionalVisible && hasAds)
{
if (hasCollections)
{
EditorGUILayout.Space();
}
EditorGUILayout.LabelField("Optional", EditorStyles.miniBoldLabel, GUILayout.Width(listWidth));
DrawPackageListItems(filteredAdPackages);
}
}
GUILayout.EndScrollView();
}
/// <summary>
/// Draw the individual collections, eg Installed and Optional
/// </summary>
/// <param name="packages"></param>
private void DrawPackageListItems( List<Package> packages)
{
foreach (Package package in packages)
{
Color prevColor = GUI.backgroundColor;
{
selectedPackage = selectedTabItems.GetValueOrDefault(tab);
if (package.IsInGroup( selectedPackage))
{
GUI.backgroundColor = selectionColor;
}
if (GUILayout.Button(package.packName, GUILayout.Width(headerWidth+10)))
{
selectedPackage = package;
selectedTabItems[tab] = selectedPackage;
}
}
GUI.backgroundColor = prevColor;
}
}
public bool IsDescriptionVisible()
{
return descriptionVisible;
}
public bool IsHelpBoxVisible()
{
return helpBoxVisible;
}
public int GetListWidth()
{
return listWidth;
}
}
}