#if UNITY_EDITOR using UnityEngine; using System; using System.Collections.Generic; using UnityEditor; namespace GSpawn { [Serializable] public class DecorRule { public GameObject decoratedPrefab = null; Vector3 _relativePosition = Vector3.zero; float _relativeDistance = 0.0f; public Quaternion relativeRotation = Quaternion.identity; public Vector3 relativePosition { get { return _relativePosition; } set { _relativePosition = value; _relativeDistance = _relativePosition.magnitude; } } public float relativeDistance { get { return _relativeDistance; } } public bool isSameRule(DecorRule other) { if (this == other) return true; if (decoratedPrefab != other.decoratedPrefab) return false; if (Vector3.Magnitude(_relativePosition - other._relativePosition) > 1e-4f) return false; if (Mathf.Abs(Quaternion.Dot(relativeRotation, other.relativeRotation) - 1.0f) > 1e-4f) return false; return true; } public DecorRule clone() { var clone = new DecorRule(); clone.decoratedPrefab = decoratedPrefab; clone._relativePosition = _relativePosition; clone._relativeDistance = _relativeDistance; clone.relativeRotation = relativeRotation; return clone; } } [Serializable] public class DecorRuleList { public int favPropsSpawnRule = 0; public List rules = new List(); public void sortRulesByRelativeDistance() { rules.Sort((DecorRule r0, DecorRule r1) => { return r0.relativeDistance.CompareTo(r1.relativeDistance); }); } } [Serializable] public class DecoratedMap : SerializableDictionary { } [Serializable] public class DecorPrefab { public GameObject decorPrefab; public DecoratedMap decoratedMap = new DecoratedMap(); public DecorPrefab(GameObject decorPrefabAsset) { decorPrefab = decorPrefabAsset; } public void setFavPropsSpawnRule(GameObject decoratedObject, int favRule) { if (decoratedObject == null) return; GameObject decoratedPrefab = decoratedObject.getOutermostPrefabAsset(); if (decoratedPrefab == null) return; DecorRuleList ruleList = null; if (decoratedMap.TryGetValue(decoratedPrefab, out ruleList)) { ruleList.favPropsSpawnRule = favRule; } } public int getFavPropsSpawnRule(GameObject decoratedObject) { if (decoratedObject == null) return 0; GameObject decoratedPrefab = decoratedObject.getOutermostPrefabAsset(); if (decoratedPrefab == null) return 0; DecorRuleList ruleList = null; if (decoratedMap.TryGetValue(decoratedPrefab, out ruleList)) { return ruleList.favPropsSpawnRule; } else return 0; } } [Serializable] public class DecorMap : SerializableDictionary { } public class DecorRuleApplyResult { public GameObject decoratedPrefab; public List rules = new List(); public DecorRule rule; public int ruleIndex; public bool applied; public void clear() { decoratedPrefab = null; rules.Clear(); rule = null; ruleIndex = -1; applied = false; } public void copy(DecorRuleApplyResult src) { decoratedPrefab = src.decoratedPrefab; rule = src.rule; ruleIndex = src.ruleIndex; applied = src.applied; rules.Clear(); rules.AddRange(src.rules); } } [Serializable] public class PrefabAssetSet : SerializableHashSet { }; [Serializable] public class DecorPrefabSet { public PrefabAssetSet decorPrefabs = new PrefabAssetSet(); } [Serializable] public class DecoratedTrackerMap : SerializableDictionary { } public class PrefabDecorRuleDb : ScriptableObject { const int _decorRuleCap = 20; private static PrefabDecorRuleDb _instance; [NonSerialized] private ObjectOverlapFilter _gatherDecoratedFilter = new ObjectOverlapFilter(); [NonSerialized] private List _decorPrefabInstances = new List(); [NonSerialized] private List _decoratedBuffer = new List(); [NonSerialized] private List _childrenAndSelfBuffer = new List(); [SerializeField] private DecorMap _decorMap = new DecorMap(); //[SerializeField] //private DecoratedTrackerMap _decoratedTracker = new DecoratedTrackerMap(); public static PrefabDecorRuleDb instance { get { if (_instance == null) _instance = AssetDbEx.loadScriptableObject(PluginFolders.pluginInternal); return _instance; } } public static bool exists { get { return _instance != null; } } /* public void getDecorPrefabs(GameObject decoratedPrefab, List decorPrefabs) { decorPrefabs.Clear(); DecorPrefabSet decorPrefabSet = null; if (_decoratedTracker.TryGetValue(decoratedPrefab, out decorPrefabSet)) { foreach (var p in decorPrefabSet.decorPrefabs) decorPrefabs.Add(p); } }*/ public DecorPrefab getDecor(GameObject decorPrefab) { DecorPrefab decor = null; if (_decorMap.TryGetValue(decorPrefab, out decor)) return decor; return null; } public void onPrefabAssetWillBeDeleted(GameObject prefabAsset) { if (_decorMap.ContainsKey(prefabAsset)) _decorMap.Remove(prefabAsset); EditorUtility.SetDirty(this); } public void getDecoratedPrefabs(GameObject decorPrefab, List decoratedPrefabs) { decoratedPrefabs.Clear(); DecorPrefab decor = null; if (_decorMap.TryGetValue(decorPrefab, out decor)) { foreach (var pair in decor.decoratedMap) decoratedPrefabs.Add(pair.Key); } } public void applyDecorRule(GameObject decorObject, GameObject decorPrefab, GameObject decoratedObject, int ruleIndex, bool applyRotation, DecorRuleApplyResult result) { result.clear(); if (decorPrefab == null || decoratedObject == null) return; GameObjectType decoratedObjectType = GameObjectDataDb.instance.getGameObjectType(decoratedObject); if (decoratedObjectType == GameObjectType.Mesh && !decoratedObject.isTerrainMesh() && !decoratedObject.isSphericalMesh()) { GameObject decoratedPrefabAsset = decoratedObject.getOutermostPrefabAsset(); if (decoratedPrefabAsset != null) { getDecorRules(decorPrefab, decoratedPrefabAsset, result.rules); if (result.rules.Count != 0) { if (ruleIndex >= result.rules.Count) ruleIndex = 0; else if (ruleIndex < 0) ruleIndex = result.rules.Count - 1; var decorRule = result.rules[ruleIndex]; decorObject.transform.position = decoratedObject.transform.TransformPoint(decorRule.relativePosition); if (applyRotation) decorObject.transform.rotation = decoratedObject.transform.rotation * decorRule.relativeRotation; result.applied = true; result.rule = decorRule; result.ruleIndex = ruleIndex; result.decoratedPrefab = decoratedPrefabAsset; } } } } public void getDecorRules(GameObject decorPrefab, GameObject decoratedPrefab, List decorRules) { decorRules.Clear(); DecorPrefab decor = null; if (_decorMap.TryGetValue(decorPrefab, out decor)) { DecorRuleList ruleList = null; if (decor.decoratedMap.TryGetValue(decoratedPrefab, out ruleList)) decorRules.AddRange(ruleList.rules); } } public void getDecorRules(GameObject decorPrefab, List decorRules) { decorRules.Clear(); DecorPrefab decor = null; if (_decorMap.TryGetValue(decorPrefab, out decor)) { foreach (var pair in decor.decoratedMap) decorRules.AddRange(pair.Value.rules); } } [NonSerialized] private List _profilePrefabAssets = new List(); [NonSerialized] private List _decorRuleBuffer = new List(); [NonSerialized] private List _otherDecorRuleBuffer = new List(); public void generateDecorRules(PrefabLibProfile libProfile) { UndoEx.saveEnabledState(); UndoEx.enabled = false; _decorMap.Clear(); //_decoratedTracker.Clear(); libProfile.getAllPrefabAssets(_profilePrefabAssets); int numProfilePrefabAssets = _profilePrefabAssets.Count; PluginProgressDialog.begin("Generating Decor Rules (" + numProfilePrefabAssets + " prefabs)"); for (int profilePrefabIndex = 0; profilePrefabIndex < numProfilePrefabAssets; ++profilePrefabIndex) { var decorPrefabAsset = _profilePrefabAssets[profilePrefabIndex]; PluginProgressDialog.updateProgress(decorPrefabAsset.name, (profilePrefabIndex + 1) / (float)numProfilePrefabAssets); generateDecorRules(decorPrefabAsset, _profilePrefabAssets); } PluginProgressDialog.end(); // Copy decor rules between prefabs with similar names PrefabCategoryName prefabCategoryName = new PrefabCategoryName(); PluginProgressDialog.begin("Syncing Similar Prefabs"); for (int profilePrefabIndex = 0; profilePrefabIndex < numProfilePrefabAssets; ++profilePrefabIndex) { var profilePrefab = _profilePrefabAssets[profilePrefabIndex]; PluginProgressDialog.updateProgress(profilePrefab.name, (profilePrefabIndex + 1) / (float)numProfilePrefabAssets); DecorPrefab decor = null; if (!_decorMap.TryGetValue(profilePrefab, out decor)) { decor = new DecorPrefab(profilePrefab); _decorMap.Add(profilePrefab, decor); } int numDecorated = decor.decoratedMap.Count; prefabCategoryName.extract(profilePrefab.name); for (int otherIndex = 0; otherIndex < numProfilePrefabAssets; ++otherIndex) { if (otherIndex == profilePrefabIndex) continue; var otherProfilePrefab = _profilePrefabAssets[otherIndex]; if (prefabCategoryName.matchPrefabName(otherProfilePrefab.name)) { DecorPrefab otherDecor = null; if (!_decorMap.TryGetValue(otherProfilePrefab, out otherDecor)) { otherDecor = new DecorPrefab(otherProfilePrefab); _decorMap.Add(otherProfilePrefab, otherDecor); } int otherNumDecorated = otherDecor.decoratedMap.Count; if (numDecorated != 0 && otherNumDecorated == 0) otherDecor.decoratedMap = decor.decoratedMap; else if (numDecorated == 0 && otherNumDecorated != 0) decor.decoratedMap = otherDecor.decoratedMap; else if (numDecorated > otherNumDecorated) { getDecorRules(profilePrefab, _decorRuleBuffer); int numRules = _decorRuleBuffer.Count; for (int ruleIndex = 0; ruleIndex < numRules; ++ruleIndex) addDecorRule(otherProfilePrefab, _decorRuleBuffer[ruleIndex]); } else if (numDecorated < otherNumDecorated) { getDecorRules(otherProfilePrefab, _decorRuleBuffer); int numRules = _decorRuleBuffer.Count; for (int ruleIndex = 0; ruleIndex < numRules; ++ruleIndex) addDecorRule(profilePrefab, _decorRuleBuffer[ruleIndex]); } } } } PluginProgressDialog.end(); UndoEx.restoreEnabledState(); EditorUtility.SetDirty(this); } public void generateDecorRules(List decorPrefabAssets, PrefabLibProfile libProfile) { UndoEx.saveEnabledState(); UndoEx.enabled = false; /*foreach (var pair in _decoratedTracker) { pair.Value.decorPrefabs.RemoveWhere(item => decorPrefabAssets.Contains(item)); }*/ libProfile.getAllPrefabAssets(_profilePrefabAssets); int numDecorPrefabs = decorPrefabAssets.Count; PluginProgressDialog.begin("Generating Decor Rules (" + numDecorPrefabs + " prefabs)"); for (int decorPrefabIndex = 0; decorPrefabIndex < numDecorPrefabs; ++decorPrefabIndex) { var decorPrefabAsset = decorPrefabAssets[decorPrefabIndex]; if (_decorMap.ContainsKey(decorPrefabAsset)) _decorMap.Remove(decorPrefabAsset); PluginProgressDialog.updateProgress(decorPrefabAsset.name, (decorPrefabIndex + 1) / (float)numDecorPrefabs); generateDecorRules(decorPrefabAsset, _profilePrefabAssets); } PluginProgressDialog.end(); // Copy decor rules between prefabs with similar names PrefabCategoryName prefabCategoryName = new PrefabCategoryName(); PluginProgressDialog.begin("Syncing Similar Prefabs"); for (int decorPrefabIndex = 0; decorPrefabIndex < numDecorPrefabs; ++decorPrefabIndex) { var decorPrefabAsset = decorPrefabAssets[decorPrefabIndex]; PluginProgressDialog.updateProgress(decorPrefabAsset.name, (decorPrefabIndex + 1) / (float)numDecorPrefabs); DecorPrefab decor = null; if (!_decorMap.TryGetValue(decorPrefabAsset, out decor)) { decor = new DecorPrefab(decorPrefabAsset); _decorMap.Add(decorPrefabAsset, decor); } int numDecorated = decor.decoratedMap.Count; prefabCategoryName.extract(decor.decorPrefab.name); foreach (var pair in _decorMap) { var otherDecor = pair.Value; if (otherDecor == decor) continue; if (prefabCategoryName.matchPrefabName(otherDecor.decorPrefab.name)) { int otherNumDecorated = otherDecor.decoratedMap.Count; if (otherNumDecorated > numDecorated) { getDecorRules(otherDecor.decorPrefab, _decorRuleBuffer); int numRules = _decorRuleBuffer.Count; for (int ruleIndex = 0; ruleIndex < numRules; ++ruleIndex) addDecorRule(decor.decorPrefab, _decorRuleBuffer[ruleIndex]); } else if (numDecorated > otherNumDecorated) { getDecorRules(decor.decorPrefab, _decorRuleBuffer); int numRules = _decorRuleBuffer.Count; for (int ruleIndex = 0; ruleIndex < numRules; ++ruleIndex) addDecorRule(otherDecor.decorPrefab, _decorRuleBuffer[ruleIndex]); } } } } PluginProgressDialog.end(); UndoEx.restoreEnabledState(); EditorUtility.SetDirty(this); } [NonSerialized] private HashSet _decoratedPrefabAssetSet = new HashSet(); private void generateDecorRules(GameObject decorPrefabAsset, List profilePrefabAssets) { _decoratedPrefabAssetSet.Clear(); ObjectBounds.QueryConfig decorBoundsQConfig = getDecorBoundsQConfig(); ObjectOverlapConfig decoratedOverlapConfig = ObjectOverlapConfig.defaultConfig; decoratedOverlapConfig.prefabMode = ObjectOverlapPrefabMode.None; _gatherDecoratedFilter.objectTypes = GameObjectType.Mesh; PluginScene.instance.findPrefabInstances(decorPrefabAsset, _decorPrefabInstances); int numInstances = _decorPrefabInstances.Count; for (int instIndex = 0; instIndex < numInstances; ++instIndex) { var decorPrefabInstance = _decorPrefabInstances[instIndex]; if (!decorPrefabInstance.activeSelf) continue; OBB decorOBB = ObjectBounds.calcHierarchyWorldOBB(decorPrefabInstance, decorBoundsQConfig); if (!decorOBB.isValid) continue; decorPrefabInstance.getAllChildrenAndSelf(true, true, _childrenAndSelfBuffer); _gatherDecoratedFilter.setIgnoredObjects(_childrenAndSelfBuffer); PluginScene.instance.overlapBox(decorOBB, _gatherDecoratedFilter, decoratedOverlapConfig, _decoratedBuffer); int numDecorated = _decoratedBuffer.Count; int decoratedIndex = 0; while (decoratedIndex < numDecorated) { var decorated = _decoratedBuffer[decoratedIndex]; if (decorated.isTerrainMesh() || decorated.isSphericalMesh()) { ++decoratedIndex; continue; } GameObject decoratedPrefabAsset = decorated.getOutermostPrefabAsset(); if (decoratedPrefabAsset == null) { ++decoratedIndex; continue; } // Note: The decor prefab OBB must intersect the decorated geometry. if (!decorated.obbIntersectsMeshTriangles(decorOBB)) { ++decoratedIndex; continue; } var decorRule = new DecorRule(); decorRule.relativePosition = decorated.transform.InverseTransformPoint(decorPrefabInstance.transform.position); decorRule.relativeRotation = Quaternion.Inverse(decorated.transform.rotation) * decorPrefabInstance.transform.rotation; decorRule.decoratedPrefab = decoratedPrefabAsset; addDecorRule(decorPrefabAsset, decorRule); _decoratedPrefabAssetSet.Add(decoratedPrefabAsset); ++decoratedIndex; } } PrefabCategoryName prefabCategoryName = new PrefabCategoryName(); int numProfilePrefabAssets = profilePrefabAssets.Count; foreach (var decoratedPrefabAsset in _decoratedPrefabAssetSet) { prefabCategoryName.extract(decoratedPrefabAsset.name); for (int prefabIndex = 0; prefabIndex < numProfilePrefabAssets; ++prefabIndex) { var maybeDecoratedPrefab = profilePrefabAssets[prefabIndex]; // Note: Don't check '_decoratedPrefabAssetSet.Contains(maybeDecoratedPrefab)'. Even if the prefab has already been // decorated, we are interested in borrowing decorations from prefabs of the same category. if (maybeDecoratedPrefab == decoratedPrefabAsset) continue; // If this prefab's name matches the category name of a prefab that has been decorated, // copy the decor rules. if (prefabCategoryName.matchPrefabName(maybeDecoratedPrefab.name)) { getDecorRules(decorPrefabAsset, decoratedPrefabAsset, _decorRuleBuffer); int numRules = _decorRuleBuffer.Count; for (int ruleIndex = 0; ruleIndex < numRules; ++ruleIndex) { var decorRule = _decorRuleBuffer[ruleIndex].clone(); decorRule.decoratedPrefab = maybeDecoratedPrefab; addDecorRule(decorPrefabAsset, decorRule); } } } } } private void addDecorRule(GameObject decorPrefab, DecorRule decorRule) { DecorPrefab decor = null; DecorRuleList ruleList = null; if (_decorMap.TryGetValue(decorPrefab, out decor)) { if (decor.decoratedMap.TryGetValue(decorRule.decoratedPrefab, out ruleList)) { if (ruleList.rules.Count < _decorRuleCap) { if (!checkForSameRules(decorRule, ruleList.rules)) { ruleList.rules.Add(decorRule); // Note: Keep array sorted. ruleList.sortRulesByRelativeDistance(); } } else { if (!checkForSameRules(decorRule, ruleList.rules)) { var rules = ruleList.rules; for (int i = 0; i < _decorRuleCap; ++i) { if (decorRule.relativeDistance < rules[i].relativeDistance) { rules[i] = decorRule; break; } } } } } else { ruleList = new DecorRuleList(); ruleList.rules.Add(decorRule); decor.decoratedMap.Add(decorRule.decoratedPrefab, ruleList); } } else { decor = new DecorPrefab(decorPrefab); _decorMap.Add(decorPrefab, decor); ruleList = new DecorRuleList(); ruleList.rules.Add(decorRule); decor.decoratedMap.Add(decorRule.decoratedPrefab, ruleList); } /*DecorPrefabSet decorPrefabSet = null; if (_decoratedTracker.TryGetValue(decorRule.decoratedPrefab, out decorPrefabSet)) { decorPrefabSet.decorPrefabs.Add(decorPrefab); } else { decorPrefabSet = new DecorPrefabSet(); decorPrefabSet.decorPrefabs.Add(decorPrefab); _decoratedTracker.Add(decorRule.decoratedPrefab, decorPrefabSet); }*/ EditorUtility.SetDirty(this); } private bool checkForSameRules(DecorRule rule, List rules) { int numRules = rules.Count; for (int i = 0; i < numRules; ++i) { if (rule.isSameRule(rules[i])) return true; } return false; } private ObjectBounds.QueryConfig getDecorBoundsQConfig() { ObjectBounds.QueryConfig decorBoundsQConfig = ObjectBounds.QueryConfig.defaultConfig; decorBoundsQConfig.objectTypes = GameObjectType.Mesh | GameObjectType.Light | GameObjectType.ParticleSystem; decorBoundsQConfig.volumelessSize = Vector3Ex.create(0.1f); decorBoundsQConfig.includeAddedObjectOverrides = false; return decorBoundsQConfig; } } } #endif