#if UNITY_EDITOR using UnityEngine; using UnityEditor; using System; using System.Collections.Generic; namespace GSpawn { public class TerrainCollection { public List unityTerrains = new List(); public List terrainMeshes = new List(); public void clear() { unityTerrains.Clear(); terrainMeshes.Clear(); } } public class PluginScene : ScriptableObject { public class PrefabPickResult { public PluginPrefab pickedPluginPrefab; public GameObject pickedObject; } [SerializeField] private PluginGrid _grid; [SerializeField] private List _grids = new List(); [NonSerialized] private SceneRaycastFilter _gridSnapRaycastFilter = new SceneRaycastFilter() { objectTypes = GameObjectType.Mesh | GameObjectType.Sprite | GameObjectType.Terrain, raycastGrid = false }; [NonSerialized] private Func _prefabInstanceFilter = new Func((GameObject go) => { return !go.couldBePooled(); }); [NonSerialized] private List _sceneObjects = new List(); [NonSerialized] private List _gameObjectBuffer = new List(); [NonSerialized] private List _rootBuffer = new List(); [NonSerialized] private List _objectDeleteBuffer = new List(); [NonSerialized] private List _childrenAndSelfBuffer = new List(); [NonSerialized] private List _objectHitBuffer = new List(); [NonSerialized] private List _vector3Buffer = new List(); [NonSerialized] private List _prefabAssetBuffer = new List(); [NonSerialized] private SceneObjectTree _objectTree = new SceneObjectTree(); [NonSerialized] private TerrainObjectAABBTree _terrainObjectTree = new TerrainObjectAABBTree(); public PluginGrid grid { get { if (_grid == null) _grid = ScriptableObject.CreateInstance(); return _grid; } } public bool snapGridToPickedObjectEnabled { get; set; } public static PluginScene instance { get { return GSpawn.active.pluginScene; } } public static float terrainOverlapBoxVerticalSize { get { return 2000.0f; } } public void refreshObjectTrees() { _objectTree.clear(); _terrainObjectTree.clear(); getSceneObjects(_sceneObjects); addObjectsToTrees(_sceneObjects); } public void onSceneGUI(SceneView sceneView) { // Note: For larger scenes, it's slow to do this here. //getSceneObjects(_sceneObjects); foreach (var sceneObject in _sceneObjects) { // Note: Could happen due to the fact that onSceneGUI seems to be called // before '_sceneObjects' has a chance to be updated when an object // is deleted. if (sceneObject == null) continue; Transform objectTransform = sceneObject.transform; if (objectTransform.hasChanged) { _objectTree.onGameObjectTransformChanged(sceneObject); objectTransform.hasChanged = false; if (sceneObject.isTerrain()) _terrainObjectTree.onTerrainObjectTransformChanged(sceneObject); } } bool drawGrid = GSpawn.active.levelDesignToolId != LevelDesignToolId.ObjectSpawn || (ObjectSpawn.instance.activeTool.spawnToolId != ObjectSpawnToolId.TileRules); if (drawGrid) { Event e = Event.current; if (FixedShortcuts.grid_enableChangeCellSize(e) && e.isScrollWheel) { float step = 0.25f; //float sign = Mathf.Sign(-e.delta.y); // For some users, this doesn't work. Could have something to do with hrz scrolling. float sign = e.getMouseScrollSign(); Vector3 cellSize = grid.activeSettings.cellSize; cellSize.x += step * sign; cellSize.z += step * sign; if (cellSize.x < step) cellSize.x = step; if (cellSize.z < step) cellSize.z = step; grid.activeSettings.cellSize = Vector3Ex.roundCorrectError(cellSize, 1e-5f); e.disable(); } drawGridHandles(sceneView.camera); } if (snapGridToPickedObjectEnabled) { Event e = Event.current; if (e.type == EventType.MouseDown) snapGridToPickedObject(e.clickCount == 2); } } public void destroyPhysicsSimulationMonos(bool showMessage) { var simObjects = FindObjectsOfType(true); foreach (var simObject in simObjects) { simObject.onExitSimulation(); PhysicsSimulationObjectMono.DestroyImmediate(simObject); } if (showMessage) { EditorUtility.DisplayDialog("Physics Simulation Object Script Removal", simObjects.Length + " physics simulation object scripts " + "have been removed.", "Ok"); } } public void onObjectSpawned(GameObject gameObject) { gameObject.getAllChildrenAndSelf(true, true, _childrenAndSelfBuffer); foreach (var go in _childrenAndSelfBuffer) { _sceneObjects.Add(go); _objectTree.registerGameObject(go); if (go.isTerrain()) _terrainObjectTree.registerTerrainObject(go); } } public void onObjectsWillBeDestroyed(List gameObjects) { foreach (var go in gameObjects) onObjectWillBeDestroyed(go); } public void onObjectWillBeDestroyed(GameObject gameObject) { gameObject.getAllChildrenAndSelf(true, true, _childrenAndSelfBuffer); foreach(var go in _childrenAndSelfBuffer) { _sceneObjects.Remove(go); _objectTree.unregisterGameObject(go); if (go.isTerrain()) _terrainObjectTree.unregisterTerrainObject(go); } } public void onObjectLayerChangedTerrainMeshStatus(PluginObjectLayer objectLayer) { if (objectLayer.isTerrainMesh) { foreach (var sceneObject in _sceneObjects) { if (sceneObject.layer == objectLayer.layerIndex && sceneObject.isTerrainMesh()) _terrainObjectTree.registerTerrainObject(sceneObject); } } else { foreach (var sceneObject in _sceneObjects) { if (sceneObject.layer == objectLayer.layerIndex && sceneObject.getMesh() != null && sceneObject.getTerrain() == null) _terrainObjectTree.unregisterTerrainObject(sceneObject); } } } public void findAllTerrains(TerrainCollection terrains) { _terrainObjectTree.getTerrains(terrains); } public void findUnityTerrains(List unityTerrains) { _terrainObjectTree.getUnityTerrains(unityTerrains); } public void findTerrainMeshes(List terrainMeshes) { _terrainObjectTree.getTerrainMeshes(terrainMeshes); } public PrefabPickResult pickPrefab(Ray ray, SceneRaycastFilter raycastFilter, ObjectRaycastConfig raycastConfig) { var rayHit = raycastClosest(ray, raycastFilter, raycastConfig); if (!rayHit.wasObjectHit) return null; GameObject prefabAsset = rayHit.objectHit.hitObject.getOutermostPrefabAsset(); if (prefabAsset == null) return null; PluginPrefab pluginPrefab = PrefabLibProfileDb.instance.getPrefab(prefabAsset); if (pluginPrefab == null) return null; return new PrefabPickResult() { pickedPluginPrefab = pluginPrefab, pickedObject = rayHit.objectHit.hitObject }; } public void snapGridToPickedObject(bool snapToClosestBoundary) { SceneRayHit rayHit = raycastClosest(PluginCamera.camera.getCursorRay(), _gridSnapRaycastFilter, ObjectRaycastConfig.defaultConfig); if (rayHit != null && rayHit.wasObjectHit) { if (snapToClosestBoundary) { ObjectBounds.QueryConfig boundsQConfig = ObjectBounds.QueryConfig.defaultConfig; boundsQConfig.objectTypes = GameObjectType.Mesh | GameObjectType.Sprite | GameObjectType.Terrain; OBB worldOBB = ObjectBounds.calcWorldOBB(rayHit.objectHit.hitObject, boundsQConfig); if (worldOBB.isValid) { worldOBB.calcCorners(_vector3Buffer, false); Plane slicePlane = new Plane(grid.up, worldOBB.center); PlaneClassifyResult ptLocation = slicePlane.classifyPoint(rayHit.objectHit.hitPoint); int snapDestIndex = -1; if (ptLocation == PlaneClassifyResult.Behind) snapDestIndex = slicePlane.findIndexOfFurthestPointBehind(_vector3Buffer); else snapDestIndex = slicePlane.findIndexOfFurthestPointInFront(_vector3Buffer); if (snapDestIndex >= 0) grid.snapToPoint(_vector3Buffer[snapDestIndex]); } } else grid.snapToPoint(rayHit.objectHit.hitPoint); } } public void drawGizmos() { } public bool overlapBox(OBB box, ObjectOverlapFilter overlapFilter, ObjectOverlapConfig overlapConfig, List overlappedObjects) { return _objectTree.overlapBox(box, overlapFilter, overlapConfig, overlappedObjects); } public bool overlapBox(OBB box, ObjectOverlapFilter overlapFilter, ObjectOverlapConfig overlapConfig) { return _objectTree.overlapBox(box, overlapFilter, overlapConfig); } [NonSerialized] private List _overlapBuffer = new List(); public bool overlapBoxAgainstMeshTriangles(OBB box, ObjectOverlapFilter overlapFilter, ObjectOverlapConfig overlapConfig) { if (_objectTree.overlapBox(box, overlapFilter, overlapConfig, _overlapBuffer)) { int numOverlapped = _overlapBuffer.Count; for (int i = 0; i < numOverlapped; ++i) { if (_overlapBuffer[i].obbIntersectsMeshTriangles(box)) return true; } } return false; } public bool terrainsOverlapBox(OBB box, TerrainObjectOverlapFilter overlapFilter, TerrainObjectOverlapConfig overlapConfig, TerrainCollection terrains) { return _terrainObjectTree.overlapBox(box, overlapFilter, overlapConfig, terrains); } public SceneRayHit raycastClosest(Ray ray, SceneRaycastFilter raycastFilter, ObjectRaycastConfig raycastConfig) { ObjectRayHit closestObjectHit = null; if (raycastFilter == null || raycastFilter.raycastObjects) { _objectTree.raycastAll(ray, raycastFilter, raycastConfig, true, _objectHitBuffer); closestObjectHit = _objectHitBuffer.Count != 0 ? _objectHitBuffer[0] : null; } return new SceneRayHit(closestObjectHit, (raycastFilter == null || raycastFilter.raycastGrid) ? raycastGrid(ray) : null); } public bool raycastAll(Ray ray, SceneRaycastFilter raycastFilter, ObjectRaycastConfig raycastConfig, bool sort, List objectHits) { return _objectTree.raycastAll(ray, raycastFilter, raycastConfig, sort, objectHits); } public GridRayHit raycastGrid(Ray ray) { float t; Plane gridPlane = grid.plane; if (gridPlane.Raycast(ray, out t)) return new GridRayHit(ray, grid, t); return null; } public TileRuleGridRayHit raycastClosestTileRuleGrid(Ray ray, List grids) { TileRuleGridRayHit closestHit = null; foreach (var grid in grids) { var hit = grid.raycast(ray); if (hit != null) { if (closestHit == null || closestHit.hitEnter > hit.hitEnter) { closestHit = hit; } } } return closestHit; } public void getSceneObjects(List sceneObjects) { sceneObjects.Clear(); var activeScene = SceneEx.getCurrent(); if (activeScene.isLoaded) { _rootBuffer.Clear(); if (_rootBuffer.Capacity <= activeScene.rootCount) _rootBuffer.Capacity = activeScene.rootCount + 1; activeScene.GetRootGameObjects(_rootBuffer); foreach (var root in _rootBuffer) { root.getAllChildrenAndSelf(true, true, _childrenAndSelfBuffer); sceneObjects.AddRange(_childrenAndSelfBuffer); } } } public void setObjectGroupActive(ObjectGroup objectGroup, bool active, bool allowUndoRedo) { if (allowUndoRedo) UndoEx.recordGameObject(objectGroup.gameObject); objectGroup.gameObject.SetActive(active); } [NonSerialized] List _nonGroupChildBuffer = new List(); public void setObjectGroupChildrenActive(ObjectGroup objectGroup, bool active, bool applyActionFilters) { objectGroup.getAllNonGroupChildren(_nonGroupChildBuffer); if (applyActionFilters) { var actionFilters = ObjectGroupDb.instance.actionFilters; foreach (var go in _nonGroupChildBuffer) { if (actionFilters.filterObject(go)) { UndoEx.recordGameObject(go); go.SetActive(active); } } } else { foreach (var go in _nonGroupChildBuffer) { UndoEx.recordGameObject(go); go.SetActive(active); } } } public void setObjectGroupsActive(List objectGroups, bool active, bool allowUndoRedo) { if (allowUndoRedo) { foreach (var objectGroup in objectGroups) { UndoEx.recordGameObject(objectGroup.gameObject); objectGroup.gameObject.SetActive(active); } } else { foreach (var objectGroup in objectGroups) objectGroup.gameObject.SetActive(active); } } public void setLayerActive(int layer, bool active, bool allowUndoRedo) { if (allowUndoRedo) UndoEx.recordGameObjects(_sceneObjects); foreach (var gameObject in _sceneObjects) { if (gameObject.layer == layer && !PluginInstanceData.instance.isPlugin(gameObject) && !gameObject.couldBePooled()) gameObject.SetActive(active); } } public void setLayersActive(List layers, bool active, bool allowUndoRedo) { if (allowUndoRedo) UndoEx.recordGameObjects(_sceneObjects); foreach(var gameObject in _sceneObjects) { if (layers.Contains(gameObject.layer) && !PluginInstanceData.instance.isPlugin(gameObject) && !gameObject.couldBePooled()) gameObject.SetActive(active); } } public void setLayerVisible(int layer, bool visible, bool allowUndoRedo) { foreach (var gameObject in _sceneObjects) { if (gameObject.layer == layer && !PluginInstanceData.instance.isPlugin(gameObject) && !gameObject.couldBePooled()) gameObject.setVisible(visible, allowUndoRedo); } } public void setLayersVisible(List layers, bool visible, bool allowUndoRedo) { foreach (var gameObject in _sceneObjects) { if (layers.Contains(gameObject.layer) && !PluginInstanceData.instance.isPlugin(gameObject) && !gameObject.couldBePooled()) gameObject.setVisible(visible, allowUndoRedo); } } public void setPrefabInstancesActive(List prefabs, bool active, bool allowUndoRedo) { findPrefabInstances(prefabs, _gameObjectBuffer); if (allowUndoRedo) UndoEx.recordGameObjects(_gameObjectBuffer); foreach (var gameObject in _gameObjectBuffer) { if (!gameObject.couldBePooled()) gameObject.SetActive(active); } } public void setPrefabInstancesActive(List prefabAssets, bool active, bool allowUndoRedo) { findPrefabInstances(prefabAssets, _gameObjectBuffer); if (allowUndoRedo) UndoEx.recordGameObjects(_gameObjectBuffer); foreach (var gameObject in _gameObjectBuffer) { // Note: Don't affect objects hidden in hierarchy because those can // be pooled objects. if (!gameObject.couldBePooled()) gameObject.SetActive(active); } } public void deletePrefabInstances(List prefabs) { findPrefabInstances(prefabs, _gameObjectBuffer); deleteObjects(_gameObjectBuffer); } public void deleteObjects(List gameObjects) { // Note: Deleting the objects might affect the selection. ObjectSelection.instance.onObjectsWillBeDeleted(gameObjects, true); GameObjectEx.getParents(gameObjects, _rootBuffer); _rootBuffer.RemoveAll(item => TileRuleGridDb.instance.isObjectChildOfTileRuleGrid(item)); GameObjectEx.getAllObjectsInHierarchies(_rootBuffer, true, true, _objectDeleteBuffer); _objectTree.unregisterGameObjects(_objectDeleteBuffer); foreach(var go in _objectDeleteBuffer) { if (go.isTerrain()) _terrainObjectTree.unregisterTerrainObject(go); } UnityEditorCommands.softDelete(_rootBuffer); } public void deleteObject(GameObject gameObject) { if (TileRuleGridDb.instance.isObjectChildOfTileRuleGrid(gameObject)) return; _gameObjectBuffer.Clear(); _gameObjectBuffer.Add(gameObject); deleteObjects(_gameObjectBuffer); } public void deleteLayers(List layers) { _gameObjectBuffer.Clear(); foreach (var gameObject in _sceneObjects) { if (layers.Contains(gameObject.layer) && !PluginInstanceData.instance.isPlugin(gameObject) && !gameObject.couldBePooled()) _gameObjectBuffer.Add(gameObject); } deleteObjects(_gameObjectBuffer); } public void deleteLayer(int layer) { _gameObjectBuffer.Clear(); foreach (var gameObject in _sceneObjects) { if (gameObject.layer == layer && !PluginInstanceData.instance.isPlugin(gameObject) && !gameObject.couldBePooled()) _gameObjectBuffer.Add(gameObject); } deleteObjects(_gameObjectBuffer); } public void findPrefabInstances(List prefabs, List instances) { PluginPrefab.getPrefabAssets(prefabs, _prefabAssetBuffer); findPrefabInstances(_prefabAssetBuffer, instances); } public void findPrefabInstances(List prefabAssets, List instances) { GameObjectEx.getOutermostPrefabInstanceRoots(_sceneObjects, prefabAssets, instances, _prefabInstanceFilter); } public void findPrefabInstances(GameObject prefabAsset, List instances) { GameObjectEx.getOutermostPrefabInstanceRoots(_sceneObjects, prefabAsset, instances, _prefabInstanceFilter); } private void drawGridHandles(Camera camera) { var config = new GridHandles.DrawConfig(); foreach(var sceneGrid in _grids) { config.cellSizeX = sceneGrid.activeSettings.cellSizeX; config.cellSizeZ = sceneGrid.activeSettings.cellSizeZ; config.fillColor = GridPrefs.instance.fillColor; config.wireColor = GridPrefs.instance.wireColor; config.right = sceneGrid.right; config.look = sceneGrid.look; config.origin = sceneGrid.origin; config.planeNormal = sceneGrid.planeNormal; config.drawCoordSystem = GridPrefs.instance.drawCoordSystem; if (config.drawCoordSystem) { config.finiteAxisLength = GridPrefs.instance.finiteAxisLength; config.infiniteXAxis = GridPrefs.instance.infiniteXAxis; config.infiniteYAxis = GridPrefs.instance.infiniteYAxis; config.infiniteZAxis = GridPrefs.instance.infiniteZAxis; config.xAxisColor = GridPrefs.instance.xAxisColor; config.yAxisColor = GridPrefs.instance.yAxisColor; config.zAxisColor = GridPrefs.instance.zAxisColor; } GridHandles.drawInfinite(config, camera); } if (FixedShortcuts.grid_enableChangeCellSize(Event.current)) { Transform cameraTransform = PluginCamera.camera.transform; Handles.BeginGUI(); var cellSize = grid.activeSettings.cellSize; string labelText = string.Format("Cell size: <{0}, {1}, {2}>", cellSize.x, cellSize.y, cellSize.z); Handles.Label(cameraTransform.position + cameraTransform.forward * (camera.nearClipPlane + 1e-3f), labelText, GUIStyleDb.instance.sceneViewInfoLabel); Handles.EndGUI(); } } private void OnEnable() { if (!_grids.Contains(grid)) _grids.Add(grid); EditorApplication.hierarchyChanged += onSceneObjectsChanged; onSceneObjectsChanged(); // Note: Just to be sure. destroyPhysicsSimulationMonos(false); } private void OnDisable() { EditorApplication.hierarchyChanged -= onSceneObjectsChanged; } private void OnDestroy() { UndoEx.record(this); var sceneGrids = new List(_grids); _grids.Clear(); foreach (var sceneGrid in sceneGrids) ScriptableObjectEx.destroyImmediate(sceneGrid); EditorApplication.hierarchyChanged -= onSceneObjectsChanged; } private void onSceneObjectsChanged() { if (GSpawn.active == null) return; _objectTree.handleNullRefs(); _terrainObjectTree.handleNullRefs(); getSceneObjects(_sceneObjects); addObjectsToTrees(_sceneObjects); } private void addObjectsToTrees(List gameObjects) { foreach (var go in gameObjects) { _objectTree.registerGameObject(go); GameObjectType objectType = GameObjectDataDb.instance.getGameObjectType(go); if (objectType == GameObjectType.Terrain) _terrainObjectTree.registerTerrainObject(go); else if (objectType == GameObjectType.Mesh && go.isTerrainMesh()) _terrainObjectTree.registerTerrainObject(go); } } } } #endif