#if UNITY_EDITOR //#define MODULAR_WALL_PREFAB_PROFILE_NO_DUPLICATE_PREFABS using UnityEngine; using UnityEditor; using System; using System.Collections.Generic; namespace GSpawn { public enum ModularWallRuleId { StraightWall = 0, InnerCorner, OuterCorner, } public enum ModularWallAxis { X = 0, Y, Z } public static class ModularWallRuleIdEx { private static int _numRuleIds; private static ModularWallRuleId[] _ruleIdArray; static ModularWallRuleIdEx() { var allIds = Enum.GetValues(typeof(ModularWallRuleId)); _numRuleIds = allIds.Length; _ruleIdArray = new ModularWallRuleId[_numRuleIds]; foreach (var item in allIds) { var ruleId = (ModularWallRuleId)item; _ruleIdArray[(int)ruleId] = ruleId; } } public static int numRuleIds { get { return _numRuleIds; } } public static ModularWallRuleId[] ruleIdArray { get { return _ruleIdArray; } } } [Serializable] public class ModularWallProperties { [SerializeField] public ModularWallAxis upAxis = ModularWallAxis.Y; [SerializeField] public ModularWallAxis forwardAxis = ModularWallAxis.Z; [SerializeField] public bool invertForwardAxis = false; [SerializeField] public ModularWallAxis innerAxis = ModularWallAxis.X; [SerializeField] public bool invertInnerAxis = false; [SerializeField] public float height = 0.0f; [SerializeField] public float forwardSize = 0.0f; } [Serializable] public class ModularWallRelativeTransform { [SerializeField] public Vector3 position; [SerializeField] public Quaternion rotation; public void reset() { position = Vector3.zero; rotation = Quaternion.identity; } } public class ModularWallPrefabProfile : Profile { private static string _innerCornerName = "innercorner"; private static string _outerCornerName = "outercorner"; private static string _middleStraightName = "middlestraight"; private static string _firstStraightName = "firststraight"; private static string _lastStraightName = "laststraight"; private static string _pillarInnerCornerBeginName = "pillarinnercornerbegin"; private static string _pillarOuterCornerBeginName = "pillaroutercornerbegin"; private static string _pillarInnerCornerEndName = "pillarinnercornerend"; private static string _pillarOuterCornerEndName = "pillaroutercornerend"; private static string _pillarInnerCornerMiddleName = "pillarinnercornermiddle"; private static string _pillarOuterCornerMiddleName = "pillaroutercornermiddle"; [SerializeField] private GameObject _examplePrefab = null; [SerializeField] private GameObject _ep_InnerCorner = null; [SerializeField] private GameObject _ep_OuterCorner = null; [SerializeField] private GameObject _ep_MidStraight = null; [SerializeField] private GameObject _ep_FirstStraight = null; [SerializeField] private GameObject _ep_LastStraight = null; [SerializeField] private GameObject _ep_PillarInnerCornerBegin = null; [SerializeField] private GameObject _ep_PillarInnerCornerEnd = null; [SerializeField] private GameObject _ep_PillarOuterCornerBegin = null; [SerializeField] private GameObject _ep_PillarOuterCornerEnd = null; [SerializeField] private GameObject _ep_PillarInnerCornerMiddle = null; [SerializeField] private GameObject _ep_PillarOuterCornerMiddle = null; [SerializeField] private ModularWallProperties _wallProperties = new ModularWallProperties(); [SerializeField] private bool _truncateForwardSize = false; [SerializeField] private bool _spawnPillars = false; [SerializeField] private string _pillarProfileName = RandomPrefabProfileDb.defaultProfileName; [SerializeField] private ModularWallRelativeTransform _innerCornerRT = new ModularWallRelativeTransform(); [SerializeField] private ModularWallRelativeTransform _outerCornerRT = new ModularWallRelativeTransform(); [SerializeField] private ModularWallRelativeTransform _straightRT = new ModularWallRelativeTransform(); [SerializeField] private ModularWallRelativeTransform _pillarMidStraightBeginRT = new ModularWallRelativeTransform(); [SerializeField] private ModularWallRelativeTransform _pillarMidStraightEndRT = new ModularWallRelativeTransform(); [SerializeField] private ModularWallRelativeTransform _pillarInnerCornerBeginRT = new ModularWallRelativeTransform(); [SerializeField] private ModularWallRelativeTransform _pillarInnerCornerEndRT = new ModularWallRelativeTransform(); [SerializeField] private ModularWallRelativeTransform _pillarOuterCornerBeginRT = new ModularWallRelativeTransform(); [SerializeField] private ModularWallRelativeTransform _pillarOuterCornerEndRT = new ModularWallRelativeTransform(); [SerializeField] private ModularWallRelativeTransform _pillarInnerCornerMiddleRT = new ModularWallRelativeTransform(); [SerializeField] private ModularWallRelativeTransform _pillarOuterCornerMiddleRT = new ModularWallRelativeTransform(); [SerializeField] private float _straight_ForwardPushDistance_Inner; [SerializeField] private float _straight_InnerPushDistance_Inner; [SerializeField] private float _straight_ForwardPushDistance_Outer; [SerializeField] private float _straight_InnerPushDistance_Outer; [NonSerialized] private ObjectBounds.QueryConfig _wallBoundsQConfig = new ObjectBounds.QueryConfig(); [NonSerialized] private ObjectBounds.QueryConfig _pillarBoundsQConfig = new ObjectBounds.QueryConfig(); private SerializedObject _serializedObject; [SerializeField] private List _straightWallPrefabs = new List(); [SerializeField] private List _innerCornerPrefabs = new List(); [SerializeField] private List _outerCornerPrefabs = new List(); [NonSerialized] private List[] _prefabListArray = new List[Enum.GetValues(typeof(ModularWallRuleId)).Length]; [NonSerialized] private CumulativeProbabilityTable _straightWallTable = new CumulativeProbabilityTable(); [NonSerialized] private CumulativeProbabilityTable _innerCornerTable = new CumulativeProbabilityTable(); [NonSerialized] private CumulativeProbabilityTable _outerCornerTable = new CumulativeProbabilityTable(); [NonSerialized] private CumulativeProbabilityTable[] _tableArray = new CumulativeProbabilityTable[Enum.GetValues(typeof(ModularWallRuleId)).Length]; [NonSerialized] private List _mdWallPrefabBuffer = new List(); [NonSerialized] private List _objectBuffer = new List(); public GameObject examplePrefab { get { return _examplePrefab; } set { if (value != null && !validateExamplePrefab(value)) return; UndoEx.record(this); _examplePrefab = value; onExamplePrefabChanged(); EditorUtility.SetDirty(this); } } public bool hasInnerOuterCorners { get { return _ep_InnerCorner != null && _ep_OuterCorner != null; } } public bool hasPillars { get { return (hasInnerCornerPillarBegin || hasOuterCornerPillarBegin || hasInnerCornerPillarEnd || hasOuterCornerPillarEnd || hasInnerCornerPillarMiddle || hasOuterCornerPillarMiddle) && pillarProfile.getNumUsedPrefabs() != 0; } } public bool hasInnerCornerPillarBegin { get { return _ep_PillarInnerCornerBegin != null; } } public bool hasOuterCornerPillarBegin { get { return _ep_PillarOuterCornerBegin != null; } } public bool hasInnerCornerPillarEnd { get { return _ep_PillarInnerCornerEnd != null; } } public bool hasOuterCornerPillarEnd { get { return _ep_PillarOuterCornerEnd != null; } } public bool hasInnerCornerPillarMiddle { get { return _ep_PillarInnerCornerMiddle != null; } } public bool hasOuterCornerPillarMiddle { get { return _ep_PillarOuterCornerMiddle != null; } } public ModularWallRelativeTransform innerCornerRT { get { return _innerCornerRT; } } public ModularWallRelativeTransform outerCornerRT { get { return _outerCornerRT; } } public ModularWallRelativeTransform straightRT { get { return _straightRT; } } public ModularWallRelativeTransform pillarMidStraightBeginRT { get { return _pillarMidStraightBeginRT; } } public ModularWallRelativeTransform pillarMidStraightEndRT { get { return _pillarMidStraightEndRT; } } public ModularWallRelativeTransform pillarInnerCornerBeginRT { get { return _pillarInnerCornerBeginRT; } } public ModularWallRelativeTransform pillarOuterCornerBeginRT { get { return _pillarOuterCornerBeginRT; } } public ModularWallRelativeTransform pillarInnerCornerEndRT { get { return _pillarInnerCornerEndRT; } } public ModularWallRelativeTransform pillarOuterCornerEndRT { get { return _pillarOuterCornerEndRT; } } public ModularWallRelativeTransform pillarInnerCornerMiddleRT { get { return _pillarInnerCornerMiddleRT; } } public ModularWallRelativeTransform pillarOuterCornerMiddleRT { get { return _pillarOuterCornerMiddleRT; } } public float straight_ForwardPushDistance_Inner { get { return _straight_ForwardPushDistance_Inner; } } public float straight_InnerPushDistance_Inner { get { return _straight_InnerPushDistance_Inner; } } public float straight_ForwardPushDistance_Outer { get { return _straight_ForwardPushDistance_Outer; } } public float straight_InnerPushDistance_Outer { get { return _straight_InnerPushDistance_Outer; } } public ModularWallAxis wallUpAxis { get { return _wallProperties.upAxis; } set { if (value == wallForwardAxis) return; UndoEx.record(this); _wallProperties.upAxis = value; updateWallProperties(); updateRelationships(); EditorUtility.SetDirty(this); } } public ModularWallAxis wallForwardAxis { get { return _wallProperties.forwardAxis; } } public bool invertFowardAxis { get { return _wallProperties.invertForwardAxis; } } public ModularWallAxis wallInnerAxis { get { return _wallProperties.innerAxis; } } public float wallHeight { get { return _wallProperties.height; } } public float wallForwardSize { get { return _wallProperties.forwardSize; } } public bool truncateForwardSize { get { return _truncateForwardSize; } set { if (value == _truncateForwardSize) return; UndoEx.record(this); _truncateForwardSize = value; updateWallProperties(); updateRelationships(); EditorUtility.SetDirty(this); } } public bool spawnPillars { get { return _spawnPillars; } set { UndoEx.record(this); _spawnPillars = value; EditorUtility.SetDirty(this); } } public string pillarProfileName { get { return _pillarProfileName; } set { UndoEx.record(this); _pillarProfileName = value; if (string.IsNullOrEmpty(_pillarProfileName)) _pillarProfileName = RandomPrefabProfileDb.defaultProfileName; EditorUtility.SetDirty(this); } } public RandomPrefabProfile pillarProfile { get { var profile = RandomPrefabProfileDb.instance.findProfile(_pillarProfileName); if (profile == null) profile = RandomPrefabProfileDb.instance.defaultProfile; return profile; } } public int numPrefabs { get { int num = 0; foreach (var prefabList in _prefabListArray) num += prefabList.Count; return num; } } public SerializedObject serializedObject { get { if (_serializedObject == null) _serializedObject = new SerializedObject(this); return _serializedObject; } } public static string pillarInnerCornerMiddleName_LowerCase { get { return _pillarInnerCornerMiddleName; } } public static string pillarOuterCornerMiddleName_LowerCase { get { return _pillarOuterCornerMiddleName; } } public void refreshPrefabData() { if (_examplePrefab != null && !validateExamplePrefab(_examplePrefab)) return; onExamplePrefabChanged(); } public bool isAnyPrefabUsed(ModularWallRuleId ruleId) { return isAnyPrefabUsed(_prefabListArray[(int)ruleId]); } public Vector3 getModularWallUpAxis(GameObject gameObject) { return gameObject.transform.modularWallToLocalAxis(wallUpAxis, false); } public Vector3 getModularWallForwardAxis(GameObject gameObject) { return gameObject.transform.modularWallToLocalAxis(wallForwardAxis, _wallProperties.invertForwardAxis); } public Vector3 getModularWallInnerAxis(GameObject gameObject) { return gameObject.transform.modularWallToLocalAxis(wallInnerAxis, _wallProperties.invertInnerAxis); } public AxisDescriptor getModularWallUpAxisDesc(GameObject gameObject) { return gameObject.transform.modularWallToLocalAxisDesc(wallUpAxis, false); } public AxisDescriptor getModularWallForwardAxisDesc(GameObject gameObject) { return gameObject.transform.modularWallToLocalAxisDesc(wallForwardAxis, _wallProperties.invertForwardAxis); } public ModularWallPrefab getPrefab(ModularWallRuleId ruleId, int prefabIndex) { var prefabList = _prefabListArray[(int)ruleId]; return prefabList[prefabIndex]; } public ModularWallPrefab getFirstUsedPrefab(ModularWallRuleId ruleId) { var prefabList = _prefabListArray[(int)ruleId]; foreach (var prefab in prefabList) { if (prefab.used) return prefab; } return null; } public ModularWallPrefab getBestPrefabForSpawnGuide() { float minVolume = float.MaxValue; ModularWallPrefab bestPrefab = null; var prefabList = _prefabListArray[(int)ModularWallRuleId.StraightWall]; foreach (var prefab in prefabList) { if (!prefab.used) continue; if (bestPrefab == null) { bestPrefab = prefab; minVolume = calcStraightWallOBB(bestPrefab.prefabAsset).volume; } else { float v = calcStraightWallOBB(prefab.prefabAsset).volume; if (v < minVolume) { bestPrefab = prefab; minVolume = v; } } } return bestPrefab != null ? bestPrefab : getFirstUsedPrefab(ModularWallRuleId.StraightWall); } public void onPrefabsSpawnChanceChanged() { refreshAllPrefabTables(); } public void onPrefabsUsedStateChanged() { refreshAllPrefabTables(); } public void onPrefabsUseDefaults() { refreshAllPrefabTables(); } public OBB calcStraightWallOBB(GameObject wallObject) { OBB obb = ObjectBounds.calcHierarchyWorldOBB(wallObject, _wallBoundsQConfig); Vector3 obbSize = obb.size; obbSize[(int)_wallProperties.forwardAxis] = _wallProperties.forwardSize; obbSize[(int)_wallProperties.upAxis] = _wallProperties.height; obb.size = obbSize; return obb; } public OBB calcPillarOBB(GameObject pillarObject) { OBB obb = ObjectBounds.calcHierarchyWorldOBB(pillarObject, _pillarBoundsQConfig); return obb; } public ObjectBounds.QueryConfig getWallBoundsQConfig() { return _wallBoundsQConfig; } public ModularWallPrefab pickPrefab(ModularWallRuleId ruleId) { var table = _tableArray[(int)ruleId]; return table.pickEntity(); } public void resetPrefabPreviews() { foreach (var prefabList in _prefabListArray) { int numPrefabs = prefabList.Count; PluginProgressDialog.begin("Refreshing Prefab Previews"); for (int prefabIndex = 0; prefabIndex < numPrefabs; ++prefabIndex) { var prefab = prefabList[prefabIndex]; PluginProgressDialog.updateItemProgress(prefab.prefabAsset.name, (prefabIndex + 1) / (float)numPrefabs); prefab.resetPreview(); } PluginProgressDialog.end(); } } public void regeneratePrefabPreviews() { foreach (var prefabList in _prefabListArray) { int numPrefabs = prefabList.Count; PluginProgressDialog.begin("Regenerating Prefab Previews"); for (int prefabIndex = 0; prefabIndex < numPrefabs; ++prefabIndex) { var prefab = prefabList[prefabIndex]; PluginProgressDialog.updateItemProgress(prefab.prefabAsset.name, (prefabIndex + 1) / (float)numPrefabs); prefab.regeneratePreview(); } PluginProgressDialog.end(); } } public void onPrefabAssetWillBeDeleted(GameObject prefabAsset) { if (prefabAsset == _examplePrefab) { clearExamplePrefabs(); foreach (var prefabList in _prefabListArray) { foreach (var mdWallPrefab in prefabList) { AssetDbEx.removeObjectFromAsset(mdWallPrefab, this); DestroyImmediate(mdWallPrefab); } prefabList.Clear(); } Debug.LogWarning("ModularWallPrefabProfile: Example prefab asset has been destroyed. All wall prefabs have been removed."); return; } foreach (var prefabList in _prefabListArray) { var prefabsToRemove = prefabList.FindAll(item => item.prefabAsset == prefabAsset); foreach (var mdWallPrefab in prefabsToRemove) { prefabList.Remove(mdWallPrefab); AssetDbEx.removeObjectFromAsset(mdWallPrefab, this); DestroyImmediate(mdWallPrefab); } } refreshAllPrefabTables(); updateWallProperties(); } public ModularWallPrefab createPrefab(PluginPrefab pluginPrefab, ModularWallRuleId ruleId) { if (_examplePrefab == null) { Debug.LogError("ModularWallPrefabProfile: Missing example prefab."); return null; } #if MODULAR_WALL_PREFAB_PROFILE_NO_DUPLICATE_PREFABS if (!containsPrefab(pluginPrefab)) #endif { UndoEx.saveEnabledState(); UndoEx.enabled = false; UndoEx.record(this); var mdWallPrefab = UndoEx.createScriptableObject(); mdWallPrefab.pluginPrefab = pluginPrefab; mdWallPrefab.name = mdWallPrefab.pluginPrefab.prefabAsset.name; _prefabListArray[(int)ruleId].Add(mdWallPrefab); AssetDbEx.addObjectToAsset(mdWallPrefab, this); EditorUtility.SetDirty(this); refreshPrefabTable(_tableArray[(int)ruleId], _prefabListArray[(int)ruleId]); UndoEx.restoreEnabledState(); UndoEx.record(this); return mdWallPrefab; } #if MODULAR_WALL_PREFAB_PROFILE_NO_DUPLICATE_PREFABS return null; #endif } public void createPrefabs(List pluginPrefabs, ModularWallRuleId ruleId, List createdPrefabs, bool appendCreated, string progressTitle) { _mdWallPrefabBuffer.Clear(); if (_examplePrefab == null) { Debug.LogError("ModularWallPrefabProfile: Missing example prefab."); return; } PluginProgressDialog.begin(progressTitle); if (!appendCreated) createdPrefabs.Clear(); UndoEx.saveEnabledState(); UndoEx.enabled = false; for (int prefabIndex = 0; prefabIndex < pluginPrefabs.Count; ++prefabIndex) { var pluginPrefab = pluginPrefabs[prefabIndex]; PluginProgressDialog.updateItemProgress(pluginPrefab.prefabAsset.name, (prefabIndex + 1) / (float)pluginPrefabs.Count); #if MODULAR_WALL_PREFAB_PROFILE_NO_DUPLICATE_PREFABS if (!containsPrefab(pluginPrefab)) #endif { var mdWallPrefab = UndoEx.createScriptableObject(); mdWallPrefab.pluginPrefab = pluginPrefab; mdWallPrefab.name = mdWallPrefab.pluginPrefab.prefabAsset.name; AssetDbEx.addObjectToAsset(mdWallPrefab, this); createdPrefabs.Add(mdWallPrefab); _mdWallPrefabBuffer.Add(mdWallPrefab); } } UndoEx.record(this); var prefabList = _prefabListArray[(int)ruleId]; foreach (var mdWallPrefab in _mdWallPrefabBuffer) prefabList.Add(mdWallPrefab); EditorUtility.SetDirty(this); refreshPrefabTable(_tableArray[(int)ruleId], prefabList); PluginProgressDialog.end(); UndoEx.restoreEnabledState(); UndoEx.record(this); } public void deletePrefab(ModularWallPrefab prefab) { if (prefab != null) { int numRules = _prefabListArray.Length; for (int i = 0; i < numRules; ++i) { var prefabList = _prefabListArray[i]; if (prefabList.Contains(prefab)) { UndoEx.record(this); prefabList.Remove(prefab); UndoEx.destroyObjectImmediate(prefab); EditorUtility.SetDirty(this); refreshPrefabTable(_tableArray[i], prefabList); break; } } } } public void deletePrefabs(List prefabs) { if (prefabs.Count != 0) { UndoEx.record(this); _mdWallPrefabBuffer.Clear(); int numRules = _prefabListArray.Length; for (int i = 0; i < numRules; ++i) { var prefabList = _prefabListArray[i]; foreach (var mdWallPrefab in prefabs) { if (prefabList.Contains(mdWallPrefab)) { prefabList.Remove(mdWallPrefab); _mdWallPrefabBuffer.Add(mdWallPrefab); } } } foreach (var prefab in _mdWallPrefabBuffer) UndoEx.destroyObjectImmediate(prefab); EditorUtility.SetDirty(this); refreshAllPrefabTables(); } } public void deletePrefabs(List pluginPrefabs) { if (pluginPrefabs.Count != 0) { UndoEx.record(this); _mdWallPrefabBuffer.Clear(); int numRules = _prefabListArray.Length; for (int i = 0; i < numRules; ++i) { var prefabList = _prefabListArray[i]; foreach (var pluginPrefab in pluginPrefabs) { for (int mdPrefabIndex = 0; mdPrefabIndex < prefabList.Count;) { var mdPrefab = prefabList[mdPrefabIndex]; if (pluginPrefabs.Contains(mdPrefab.pluginPrefab)) { prefabList.RemoveAt(mdPrefabIndex); _mdWallPrefabBuffer.Add(mdPrefab); } else ++mdPrefabIndex; } } } foreach (var prefab in _mdWallPrefabBuffer) UndoEx.destroyObjectImmediate(prefab); EditorUtility.SetDirty(this); refreshAllPrefabTables(); } } public void deleteAllPrefabs() { if (numPrefabs != 0) { UndoEx.record(this); _mdWallPrefabBuffer.Clear(); foreach (var prefabList in _prefabListArray) { _mdWallPrefabBuffer.AddRange(prefabList); prefabList.Clear(); } foreach (var prefab in _mdWallPrefabBuffer) UndoEx.destroyObjectImmediate(prefab); EditorUtility.SetDirty(this); refreshAllPrefabTables(); } } public bool containsPrefab(ModularWallPrefab prefab) { foreach (var prefabList in _prefabListArray) { if (prefabList.Contains(prefab)) return true; } return false; } public bool containsWallPiecePrefabAsset(GameObject prefabAsset) { foreach (var prefabList in _prefabListArray) { foreach (var prefab in prefabList) { if (prefab.prefabAsset == prefabAsset) return true; } } return false; } public bool containsPillarPrefabAsset(GameObject prefabAsset) { var prefabProfile = pillarProfile; return prefabProfile.containsPrefabAsset(prefabAsset); } public void getPrefabs(ModularWallRuleId ruleId, List prefabs) { prefabs.Clear(); prefabs.AddRange(_prefabListArray[(int)ruleId]); } private bool isAnyPrefabUsed(List prefabs) { foreach (var prefab in prefabs) { if (prefab.used) return true; } return false; } private void refreshAllPrefabTables() { int numTables = _tableArray.Length; for (int i = 0; i < numTables; ++i) refreshPrefabTable(_tableArray[i], _prefabListArray[i]); } private void refreshPrefabTable(CumulativeProbabilityTable prefabTable, List prefabs) { prefabTable.clear(); foreach (var prefab in prefabs) { if (prefab.used) prefabTable.addEntity(prefab, prefab.spawnChance); } prefabTable.refresh(); } private void initializeTableArray() { _tableArray[(int)ModularWallRuleId.StraightWall] = _straightWallTable; _tableArray[(int)ModularWallRuleId.InnerCorner] = _innerCornerTable; _tableArray[(int)ModularWallRuleId.OuterCorner] = _outerCornerTable; } private void initializeRulePrefabListArray() { _prefabListArray[(int)ModularWallRuleId.StraightWall] = _straightWallPrefabs; _prefabListArray[(int)ModularWallRuleId.InnerCorner] = _innerCornerPrefabs; _prefabListArray[(int)ModularWallRuleId.OuterCorner] = _outerCornerPrefabs; } private void onExamplePrefabChanged() { clearExamplePrefabs(); if (_examplePrefab != null) { _examplePrefab.getAllChildren(false, false, _objectBuffer); foreach (var child in _objectBuffer) { string childName = child.name.ToLower(); if (childName == _innerCornerName) _ep_InnerCorner = child; else if (childName == _outerCornerName) _ep_OuterCorner = child; else if (childName == _middleStraightName) _ep_MidStraight = child; else if (childName == _firstStraightName) _ep_FirstStraight = child; else if (childName == _lastStraightName) _ep_LastStraight = child; else if (childName == _pillarInnerCornerBeginName) _ep_PillarInnerCornerBegin = child; else if (childName == _pillarOuterCornerBeginName) _ep_PillarOuterCornerBegin = child; else if (childName == _pillarInnerCornerEndName) _ep_PillarInnerCornerEnd = child; else if (childName == _pillarOuterCornerEndName) _ep_PillarOuterCornerEnd = child; else if (childName == _pillarInnerCornerMiddleName) _ep_PillarInnerCornerMiddle = child; else if (childName == _pillarOuterCornerMiddleName) _ep_PillarOuterCornerMiddle = child; } } updateWallProperties(); updateRelationships(); } private GameObject getWallPieceByName(string name, List wallPieces) { string lowerName = name.ToLower(); foreach (var child in wallPieces) { string childName = child.name.ToLower(); if (childName == lowerName) return child; } return null; } private bool isWallPiecePillar(GameObject wallPiece) { string name = wallPiece.name.ToLower(); return name == _pillarInnerCornerBeginName || name == _pillarInnerCornerEndName || name == _pillarOuterCornerBeginName || name == _pillarOuterCornerEndName || name == _pillarInnerCornerMiddleName || name == _pillarOuterCornerMiddleName; } private bool isWallPieceStraight(GameObject wallPiece) { string name = wallPiece.name.ToLower(); return name == _middleStraightName || name == _firstStraightName || name == _lastStraightName; } private void clearExamplePrefabs() { _ep_InnerCorner = null; _ep_OuterCorner = null; _ep_MidStraight = null; _ep_FirstStraight = null; _ep_LastStraight = null; _ep_PillarInnerCornerBegin = null; _ep_PillarInnerCornerEnd = null; _ep_PillarOuterCornerBegin = null; _ep_PillarOuterCornerEnd = null; _ep_PillarInnerCornerMiddle = null; _ep_PillarOuterCornerMiddle = null; } private bool validateExamplePrefab(GameObject examplePrefab) { if (examplePrefab.isSceneObject()) { Debug.LogError("ModularWallPrefabProfile: Scene objects are not allowed."); return false; } bool foundInnerChild = false; bool foundOuterChild = false; bool foundMiddleStraightChild = false; bool foundFirstStraightChild = false; bool foundLastStraightChild = false; examplePrefab.getAllChildren(false, false, _objectBuffer); foreach (var child in _objectBuffer) { string childName = child.name.ToLower(); if (!foundInnerChild) { if (childName == _innerCornerName) { foundInnerChild = true; if (!child.hierarchyHasMesh(false, false)) { showHierarchyNoMeshMessage(child.name); return false; } continue; } } if (!foundOuterChild) { if (childName == _outerCornerName) { foundOuterChild = true; if (!child.hierarchyHasMesh(false, false)) { showHierarchyNoMeshMessage(child.name); return false; } continue; } } if (!foundMiddleStraightChild) { if (childName == _middleStraightName) { foundMiddleStraightChild = true; if (!child.hierarchyHasMesh(false, false)) { showHierarchyNoMeshMessage(child.name); return false; } // Note: Must have 0 rotation along all axes. Vector3 rotation = child.transform.rotation.eulerAngles; if (rotation != Vector3.zero) { Debug.LogError("ModularWallPrefabProfile: Middle Straight piece requires 0 rotation around all axes."); return false; } continue; } } if (!foundFirstStraightChild) { if (childName == _firstStraightName) { foundFirstStraightChild = true; if (!child.hierarchyHasMesh(false, false)) { showHierarchyNoMeshMessage(child.name); return false; } continue; } } if (!foundLastStraightChild) { if (childName == _lastStraightName) { foundLastStraightChild = true; if (!child.hierarchyHasMesh(false, false)) { showHierarchyNoMeshMessage(child.name); return false; } continue; } } } if (!foundMiddleStraightChild) { Debug.LogError("ModularWallPrefabProfile: Middle Straight piece is missing from example prefab."); return false; } if (!foundFirstStraightChild) { Debug.LogError("ModularWallPrefabProfile: First Straight piece is missing from example prefab."); return false; } if (!foundLastStraightChild) { Debug.LogError("ModularWallPrefabProfile: Last Straight piece is missing from example prefab."); return false; } return true; } private void showHierarchyNoMeshMessage(string hierarchyName) { Debug.LogError("ModularWallPrefabProfile: Invalid hierarchy '" + hierarchyName + "'. The hierarchy doesn't contain a mesh."); } private void updateRelationships() { if (_examplePrefab == null) { _innerCornerRT.reset(); _outerCornerRT.reset(); _straightRT.reset(); _pillarMidStraightBeginRT.reset(); _pillarMidStraightEndRT.reset(); _pillarInnerCornerBeginRT.reset(); _pillarInnerCornerEndRT.reset(); _pillarOuterCornerBeginRT.reset(); _pillarOuterCornerEndRT.reset(); _pillarInnerCornerMiddleRT.reset(); _pillarOuterCornerMiddleRT.reset(); _straight_ForwardPushDistance_Inner = 0.0f; _straight_InnerPushDistance_Inner = 0.0f; _straight_ForwardPushDistance_Outer = 0.0f; _straight_InnerPushDistance_Outer = 0.0f; return; } OBB midStraightOBB = calcStraightWallOBB(_ep_MidStraight); Quaternion inverseRotation = Quaternion.Inverse(midStraightOBB.rotation); _straightRT.position = inverseRotation * (_ep_MidStraight.transform.position - midStraightOBB.center); _straightRT.rotation = inverseRotation * _ep_MidStraight.transform.rotation; if (hasInnerOuterCorners) { calcInnerCornerRelationships(); calcOuterCornerRelationships(); } if (hasPillars) calcPillarRelationships(); Vector3 middleStraightFwAxis = getModularWallForwardAxis(_ep_MidStraight); OBB lastStraightOBB = calcStraightWallOBB(_ep_LastStraight); _straight_ForwardPushDistance_Inner = Vector3Ex.absDot((lastStraightOBB.center - midStraightOBB.center), middleStraightFwAxis); _straight_ForwardPushDistance_Inner -= _wallProperties.forwardSize; _straight_ForwardPushDistance_Inner = MathEx.roundCorrectError(_straight_ForwardPushDistance_Inner, 1e-5f); Vector3 pushAxis = -getModularWallInnerAxis(_ep_MidStraight); Vector3 pushedCenter = lastStraightOBB.center + pushAxis * _wallProperties.forwardSize; _straight_InnerPushDistance_Inner = Vector3Ex.absDot((pushedCenter - midStraightOBB.center), pushAxis); _straight_InnerPushDistance_Inner = MathEx.roundCorrectError(_straight_InnerPushDistance_Inner, 1e-5f); OBB firstStraightOBB = calcStraightWallOBB(_ep_FirstStraight); _straight_ForwardPushDistance_Outer = Vector3Ex.absDot((firstStraightOBB.center - midStraightOBB.center), middleStraightFwAxis); _straight_ForwardPushDistance_Outer -= _wallProperties.forwardSize; _straight_ForwardPushDistance_Outer = MathEx.roundCorrectError(_straight_ForwardPushDistance_Outer, 1e-5f); pushAxis = getModularWallInnerAxis(_ep_MidStraight); pushedCenter = firstStraightOBB.center + pushAxis * _wallProperties.forwardSize; _straight_InnerPushDistance_Outer = Vector3Ex.absDot((pushedCenter - midStraightOBB.center), pushAxis); _straight_InnerPushDistance_Outer = MathEx.roundCorrectError(_straight_InnerPushDistance_Outer, 1e-5f); } private void calcInnerCornerRelationships() { OBB lastStraightOBB = calcStraightWallOBB(_ep_LastStraight); Vector3 lastFWAxis = getModularWallForwardAxis(_ep_LastStraight); OBB midStraightOBB = calcStraightWallOBB(_ep_MidStraight); Vector3 midFWAxis = getModularWallForwardAxis(_ep_MidStraight); // Note: Average inner axes (forward axes transposed). Vector3 avgAxis = (lastFWAxis - midFWAxis).normalized; lastStraightOBB.center -= lastFWAxis * _wallProperties.forwardSize; midStraightOBB.center += midFWAxis * _wallProperties.forwardSize; Vector3 avgCenter = (lastStraightOBB.center + midStraightOBB.center) / 2.0f; Quaternion inverseRotation = Quaternion.Inverse(Quaternion.LookRotation(avgAxis, getModularWallUpAxis(_ep_MidStraight))); _innerCornerRT.position = inverseRotation * (_ep_InnerCorner.transform.position - avgCenter); _innerCornerRT.rotation = inverseRotation * _ep_InnerCorner.transform.rotation; } private void calcOuterCornerRelationships() { OBB firstStraightOBB = calcStraightWallOBB(_ep_FirstStraight); Vector3 firstFWAxis = getModularWallForwardAxis(_ep_FirstStraight); OBB midStraightOBB = calcStraightWallOBB(_ep_MidStraight); Vector3 midFWAxis = getModularWallForwardAxis(_ep_MidStraight); // Note: Average inner axes (forward axes transposed). Vector3 avgAxis = (firstFWAxis - midFWAxis).normalized; firstStraightOBB.center += firstFWAxis * _wallProperties.forwardSize; midStraightOBB.center -= midFWAxis * _wallProperties.forwardSize; Vector3 avgCenter = (firstStraightOBB.center + midStraightOBB.center) / 2.0f; Quaternion inverseRotation = Quaternion.Inverse(Quaternion.LookRotation(avgAxis, getModularWallUpAxis(_ep_MidStraight))); _outerCornerRT.position = inverseRotation * (_ep_OuterCorner.transform.position - avgCenter); _outerCornerRT.rotation = inverseRotation * _ep_OuterCorner.transform.rotation; } private void calcPillarRelationships() { if (_ep_PillarInnerCornerBegin != null) { Quaternion inverseRotation = Quaternion.Inverse(_ep_MidStraight.transform.rotation); _pillarMidStraightEndRT.position = inverseRotation * (_ep_PillarInnerCornerBegin.transform.position - _ep_MidStraight.transform.position); _pillarMidStraightEndRT.rotation = inverseRotation * _ep_PillarInnerCornerBegin.transform.rotation; } else _pillarMidStraightEndRT.reset(); if (_ep_PillarOuterCornerEnd != null) { Quaternion inverseRotation = Quaternion.Inverse(_ep_MidStraight.transform.rotation); _pillarMidStraightBeginRT.position = inverseRotation * (_ep_PillarOuterCornerEnd.transform.position - _ep_MidStraight.transform.position); _pillarMidStraightBeginRT.rotation = inverseRotation * _ep_PillarOuterCornerEnd.transform.rotation; } else _pillarMidStraightBeginRT.reset(); if (_ep_PillarInnerCornerBegin != null) { if (_ep_InnerCorner != null) { Quaternion inverseRotation = Quaternion.Inverse(_ep_InnerCorner.transform.rotation); _pillarInnerCornerBeginRT.position = inverseRotation * (_ep_PillarInnerCornerBegin.transform.position - _ep_InnerCorner.transform.position); _pillarInnerCornerBeginRT.rotation = inverseRotation * _ep_PillarInnerCornerBegin.transform.rotation; } else { // When no inner corner prefab is available, calculate the transform relative to the first imaginary corner part. GameObject firstPart, secondPart; getImaginaryInnerCornerParts(out firstPart, out secondPart); Quaternion inverseRotation = Quaternion.Inverse(firstPart.transform.rotation); _pillarInnerCornerBeginRT.position = inverseRotation * (_ep_PillarInnerCornerBegin.transform.position - calcStraightWallOBB(firstPart).center); _pillarInnerCornerBeginRT.rotation = inverseRotation * _ep_PillarInnerCornerBegin.transform.rotation; } } else _pillarInnerCornerBeginRT.reset(); if (_ep_PillarInnerCornerEnd != null) { if (_ep_InnerCorner != null) { Quaternion inverseRotation = Quaternion.Inverse(_ep_InnerCorner.transform.rotation); _pillarInnerCornerEndRT.position = inverseRotation * (_ep_PillarInnerCornerEnd.transform.position - _ep_InnerCorner.transform.position); _pillarInnerCornerEndRT.rotation = inverseRotation * _ep_PillarInnerCornerEnd.transform.rotation; } else { // When no inner corner prefab is available, calculate the transform relative to the second imaginary corner part. GameObject firstPart, secondPart; getImaginaryInnerCornerParts(out firstPart, out secondPart); Quaternion inverseRotation = Quaternion.Inverse(secondPart.transform.rotation); _pillarInnerCornerEndRT.position = inverseRotation * (_ep_PillarInnerCornerEnd.transform.position - calcStraightWallOBB(secondPart).center); _pillarInnerCornerEndRT.rotation = inverseRotation * _ep_PillarInnerCornerEnd.transform.rotation; } } else _pillarInnerCornerEndRT.reset(); if (_ep_PillarOuterCornerBegin != null) { if (_ep_OuterCorner != null) { Quaternion inverseRotation = Quaternion.Inverse(_ep_OuterCorner.transform.rotation); _pillarOuterCornerBeginRT.position = inverseRotation * (_ep_PillarOuterCornerBegin.transform.position - _ep_OuterCorner.transform.position); _pillarOuterCornerBeginRT.rotation = inverseRotation * _ep_PillarOuterCornerBegin.transform.rotation; } else { GameObject firstPart, secondPart; getImaginaryOuterCornerParts(out firstPart, out secondPart); Quaternion inverseRotation = Quaternion.Inverse(firstPart.transform.rotation); _pillarOuterCornerBeginRT.position = inverseRotation * (_ep_PillarOuterCornerBegin.transform.position - calcStraightWallOBB(firstPart).center); _pillarOuterCornerBeginRT.rotation = inverseRotation * _ep_PillarOuterCornerBegin.transform.rotation; } } else _pillarOuterCornerBeginRT.reset(); if (_ep_PillarOuterCornerEnd != null) { if (_ep_OuterCorner != null) { Quaternion inverseRotation = Quaternion.Inverse(_ep_OuterCorner.transform.rotation); _pillarOuterCornerEndRT.position = inverseRotation * (_ep_PillarOuterCornerEnd.transform.position - _ep_OuterCorner.transform.position); _pillarOuterCornerEndRT.rotation = inverseRotation * _ep_PillarOuterCornerEnd.transform.rotation; } else { GameObject firstPart, secondPart; getImaginaryOuterCornerParts(out firstPart, out secondPart); Quaternion inverseRotation = Quaternion.Inverse(secondPart.transform.rotation); _pillarOuterCornerEndRT.position = inverseRotation * (_ep_PillarOuterCornerEnd.transform.position - calcStraightWallOBB(secondPart).center); _pillarOuterCornerEndRT.rotation = inverseRotation * _ep_PillarOuterCornerEnd.transform.rotation; } } else _pillarOuterCornerEndRT.reset(); if (_ep_PillarInnerCornerMiddle != null) { if (_ep_InnerCorner != null) { Quaternion inverseRotation = Quaternion.Inverse(_ep_InnerCorner.transform.rotation); _pillarInnerCornerMiddleRT.position = inverseRotation * (_ep_PillarInnerCornerMiddle.transform.position - _ep_InnerCorner.transform.position); _pillarInnerCornerMiddleRT.rotation = inverseRotation * _ep_PillarInnerCornerMiddle.transform.rotation; } else { GameObject firstPart, secondPart; getImaginaryInnerCornerParts(out firstPart, out secondPart); OBB lastStraightOBB = calcStraightWallOBB(_ep_LastStraight); OBB middleStraightOBB = calcStraightWallOBB(_ep_MidStraight); OBB firstPartOBB = calcStraightWallOBB(firstPart); OBB secondPartOBB = calcStraightWallOBB(secondPart); Vector3 parentPosition = (firstPartOBB.center + secondPartOBB.center) * 0.5f; Vector3 look = (lastStraightOBB.center - secondPartOBB.center).normalized; Vector3 right = (middleStraightOBB.center - firstPartOBB.center).normalized; Vector3 up = Vector3.Cross(look, right).normalized; Quaternion inverseRotation = Quaternion.Inverse(Quaternion.LookRotation(look, up)); _pillarInnerCornerMiddleRT.position = inverseRotation * (_ep_PillarInnerCornerMiddle.transform.position - parentPosition); _pillarInnerCornerMiddleRT.rotation = inverseRotation * _ep_PillarInnerCornerMiddle.transform.rotation; } } else _pillarInnerCornerMiddleRT.reset(); if (_ep_PillarOuterCornerMiddle != null) { if (_ep_OuterCorner != null) { Quaternion inverseRotation = Quaternion.Inverse(_ep_OuterCorner.transform.rotation); _pillarOuterCornerMiddleRT.position = inverseRotation * (_ep_PillarOuterCornerMiddle.transform.position - _ep_OuterCorner.transform.position); _pillarOuterCornerMiddleRT.rotation = inverseRotation * _ep_PillarOuterCornerMiddle.transform.rotation; } else { GameObject firstPart, secondPart; getImaginaryOuterCornerParts(out firstPart, out secondPart); OBB firstStraightOBB = calcStraightWallOBB(_ep_FirstStraight); OBB middleStraightOBB = calcStraightWallOBB(_ep_MidStraight); OBB firstPartOBB = calcStraightWallOBB(firstPart); OBB secondPartOBB = calcStraightWallOBB(secondPart); Vector3 parentPosition = (firstPartOBB.center + secondPartOBB.center) * 0.5f; Vector3 look = (firstStraightOBB.center - firstPartOBB.center).normalized; Vector3 right = (middleStraightOBB.center - secondPartOBB.center).normalized; Vector3 up = Vector3.Cross(look, right).normalized; Quaternion inverseRotation = Quaternion.Inverse(Quaternion.LookRotation(look, up)); _pillarOuterCornerMiddleRT.position = inverseRotation * (_ep_PillarOuterCornerMiddle.transform.position - parentPosition); _pillarOuterCornerMiddleRT.rotation = inverseRotation * _ep_PillarOuterCornerMiddle.transform.rotation; } } else _pillarOuterCornerMiddleRT.reset(); } private void getImaginaryInnerCornerParts(out GameObject firstPart, out GameObject secondPart) { firstPart = null; secondPart = null; if (hasInnerOuterCorners) return; _examplePrefab.getAllChildren(false, false, _objectBuffer); var lastStraight = getWallPieceByName(_lastStraightName, _objectBuffer); var lastStraightOBB = calcStraightWallOBB(lastStraight); lastStraightOBB.inflate(1e-2f); foreach (var piece in _objectBuffer) { if (isWallPiecePillar(piece) || isWallPieceStraight(piece)) continue; OBB pieceOBB = ObjectBounds.calcHierarchyWorldOBB(piece, getWallBoundsQConfig()); if (!pieceOBB.isValid) continue; if (pieceOBB.intersectsOBB(lastStraightOBB)) { secondPart = piece; break; } } if (secondPart == null) return; var secondPartOBB = ObjectBounds.calcHierarchyWorldOBB(secondPart, getWallBoundsQConfig()); foreach (var piece in _objectBuffer) { if (isWallPiecePillar(piece) || isWallPieceStraight(piece) || piece == secondPart) continue; OBB pieceOBB = ObjectBounds.calcHierarchyWorldOBB(piece, getWallBoundsQConfig()); if (!pieceOBB.isValid) continue; if (pieceOBB.intersectsOBB(secondPartOBB)) { firstPart = piece; break; } } } private void getImaginaryOuterCornerParts(out GameObject firstPart, out GameObject secondPart) { firstPart = null; secondPart = null; if (hasInnerOuterCorners) return; _examplePrefab.getAllChildren(false, false, _objectBuffer); var firstStraight = getWallPieceByName(_firstStraightName, _objectBuffer); var firstStraightOBB = calcStraightWallOBB(firstStraight); firstStraightOBB.inflate(1e-2f); foreach (var piece in _objectBuffer) { if (isWallPiecePillar(piece) || isWallPieceStraight(piece)) continue; OBB pieceOBB = ObjectBounds.calcHierarchyWorldOBB(piece, getWallBoundsQConfig()); if (!pieceOBB.isValid) continue; if (pieceOBB.intersectsOBB(firstStraightOBB)) { firstPart = piece; break; } } if (firstPart == null) return; var firstPartOBB = ObjectBounds.calcHierarchyWorldOBB(firstPart, getWallBoundsQConfig()); foreach (var piece in _objectBuffer) { if (isWallPiecePillar(piece) || isWallPieceStraight(piece) || piece == firstPart) continue; OBB pieceOBB = ObjectBounds.calcHierarchyWorldOBB(piece, getWallBoundsQConfig()); if (!pieceOBB.isValid) continue; if (pieceOBB.intersectsOBB(firstPartOBB)) { secondPart = piece; break; } } } private void detectForwardAxis() { // Note: Requires wall pieces which have the forward size larger then the inner size. // Note: Don't use 'calcStraightWallOBB' here because it uses info which is not yet available. OBB middleStraightOBB = ObjectBounds.calcHierarchyWorldOBB(_ep_MidStraight, getWallBoundsQConfig()); Vector3 obbSize = middleStraightOBB.size; int a0 = ((int)_wallProperties.upAxis + 1) % 3; int a1 = ((int)_wallProperties.upAxis + 2) % 3; int fwAxisIndex = obbSize[a0] < obbSize[a1] ? a1 : a0; _wallProperties.forwardAxis = (ModularWallAxis)fwAxisIndex; _wallProperties.invertForwardAxis = false; Vector3 fwAxis = _ep_MidStraight.transform.getLocalAxis(fwAxisIndex); Vector3 vec = _ep_LastStraight.transform.position - _ep_MidStraight.transform.position; if (Vector3.Dot(vec, fwAxis) < 0.0f) _wallProperties.invertForwardAxis = true; } private void detectInnerAxis() { int fwAxisIndex = (int)wallForwardAxis; int upAxisIndex = (int)wallUpAxis; if (Mathf.Abs(fwAxisIndex - upAxisIndex) == 2) _wallProperties.innerAxis = ModularWallAxis.Y; else { if (fwAxisIndex > upAxisIndex) _wallProperties.innerAxis = (ModularWallAxis)((fwAxisIndex + 1) % 3); else _wallProperties.innerAxis = (ModularWallAxis)((upAxisIndex + 1) % 3); } _wallProperties.invertInnerAxis = false; Vector3 vec = _ep_LastStraight.transform.position - _ep_MidStraight.transform.position; if (Vector3.Dot(vec, getModularWallInnerAxis(_ep_MidStraight)) < 0.0f) _wallProperties.invertInnerAxis = !_wallProperties.invertInnerAxis; } private void updateWallProperties() { if (_examplePrefab == null) { _wallProperties.height = 0.0f; _wallProperties.forwardSize = 0.0f; return; } detectForwardAxis(); detectInnerAxis(); GameObject straightPrefab = _ep_MidStraight; Vector3 modelSize = PrefabDataDb.instance.getData(straightPrefab).modelSize; _wallProperties.height = modelSize[(int)_wallProperties.upAxis]; if (_truncateForwardSize) _wallProperties.forwardSize = (int)modelSize[(int)_wallProperties.forwardAxis]; else _wallProperties.forwardSize = modelSize[(int)_wallProperties.forwardAxis]; } private void OnEnable() { initializeTableArray(); initializeRulePrefabListArray(); refreshAllPrefabTables(); _wallBoundsQConfig.includeInactive = false; _wallBoundsQConfig.includeInvisible = false; _wallBoundsQConfig.objectTypes = GameObjectType.Mesh; _pillarBoundsQConfig.includeInvisible = false; _pillarBoundsQConfig.includeInactive = false; _pillarBoundsQConfig.objectTypes = GameObjectType.Mesh; Undo.undoRedoPerformed += onUndoRedo; } private void OnDisable() { Undo.undoRedoPerformed -= onUndoRedo; } private void onUndoRedo() { refreshAllPrefabTables(); } private void OnDestroy() { deleteAllPrefabs(); } } } #endif