123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- /*
- Copyright (c) 2020 Omar Duarte
- Unauthorized copying of this file, via any medium is strictly prohibited.
- Writen by Omar Duarte, 2020.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- THE SOFTWARE.
- */
- using System.Linq;
- using UnityEngine;
- namespace PluginMaster
- {
- #region DATA & SETTINGS
- [System.Serializable]
- public class BrushToolSettings : BrushToolBase, IPaintOnSurfaceToolSettings, ISerializationCallbackReceiver
- {
- [SerializeField] private PaintOnSurfaceToolSettings _paintOnSurfaceToolSettings = new PaintOnSurfaceToolSettings();
- [SerializeField] private float _maxHeightFromCenter = 2f;
- public enum HeightType { CUSTOM, RADIUS }
- [SerializeField] private HeightType _heightType = HeightType.RADIUS;
- public enum AvoidOverlappingType
- {
- DISABLED,
- WITH_PALETTE_PREFABS,
- WITH_BRUSH_PREFABS,
- WITH_SAME_PREFABS,
- WITH_ALL_OBJECTS
- }
- [SerializeField] private AvoidOverlappingType _avoidOverlapping = AvoidOverlappingType.WITH_ALL_OBJECTS;
- [SerializeField] private LayerMask _layerFilter = -1;
- [SerializeField] private System.Collections.Generic.List<string> _tagFilter = null;
- [SerializeField] private RandomUtils.Range _slopeFilter = new RandomUtils.Range(0, 60);
- [SerializeField] private string[] _terrainLayerIds = null;
- [SerializeField] private bool _showPreview = false;
- private TerrainLayer[] _terrainLayerFilter = null;
- private bool _updateTerrainFilter = false;
- private long id = 0;
- public BrushToolSettings() : base()
- {
- id = System.DateTime.Now.Ticks;
- _paintOnSurfaceToolSettings.OnDataChanged += DataChanged;
- }
- public bool paintOnMeshesWithoutCollider
- {
- get => _paintOnSurfaceToolSettings.paintOnMeshesWithoutCollider;
- set => _paintOnSurfaceToolSettings.paintOnMeshesWithoutCollider = value;
- }
- public bool paintOnSelectedOnly
- {
- get => _paintOnSurfaceToolSettings.paintOnSelectedOnly;
- set => _paintOnSurfaceToolSettings.paintOnSelectedOnly = value;
- }
- public bool paintOnPalettePrefabs
- {
- get => _paintOnSurfaceToolSettings.paintOnPalettePrefabs;
- set => _paintOnSurfaceToolSettings.paintOnPalettePrefabs = value;
- }
- public bool showPreview
- {
- get => _showPreview;
- set
- {
- if (_showPreview == value) return;
- _showPreview = value;
- DataChanged();
- }
- }
- public float maxHeightFromCenter
- {
- get => _maxHeightFromCenter;
- set
- {
- if (_maxHeightFromCenter == value) return;
- _maxHeightFromCenter = value;
- DataChanged();
- }
- }
- public HeightType heightType
- {
- get => _heightType;
- set
- {
- if (_heightType == value) return;
- _heightType = value;
- DataChanged();
- }
- }
- public AvoidOverlappingType avoidOverlapping
- {
- get => _avoidOverlapping;
- set
- {
- if (_avoidOverlapping == value) return;
- _avoidOverlapping = value;
- DataChanged();
- }
- }
- public virtual LayerMask layerFilter
- {
- get => _layerFilter;
- set
- {
- if (_layerFilter == value) return;
- _layerFilter = value;
- DataChanged();
- }
- }
- public virtual System.Collections.Generic.List<string> tagFilter
- {
- get
- {
- if (_tagFilter == null) UpdateTagFilter();
- return _tagFilter;
- }
- set
- {
- if (_tagFilter == value) return;
- _tagFilter = value;
- DataChanged();
- }
- }
- public virtual RandomUtils.Range slopeFilter
- {
- get => _slopeFilter;
- set
- {
- if (_slopeFilter == value) return;
- _slopeFilter = value;
- DataChanged();
- }
- }
- public TerrainLayer[] terrainLayerFilter
- {
- get
- {
- if ((_terrainLayerFilter == null && _terrainLayerIds != null) || _updateTerrainFilter) UpdateTerrainFilter();
- return _terrainLayerFilter;
- }
- set
- {
- if (Equals(_terrainLayerFilter, value)) return;
- if (value == null)
- {
- _terrainLayerFilter = null;
- _terrainLayerIds = null;
- return;
- }
- var layerList = new System.Collections.Generic.List<TerrainLayer>();
- var terrainLayerIds = new System.Collections.Generic.List<string>();
- foreach (var layer in value)
- {
- layerList.Add(layer);
- if (layer == null) continue;
- terrainLayerIds.Add(UnityEditor.GlobalObjectId.GetGlobalObjectIdSlow(layer).ToString());
- }
- _terrainLayerFilter = layerList.ToArray();
- _terrainLayerIds = terrainLayerIds.ToArray();
- }
- }
- public override void Copy(IToolSettings other)
- {
- var otherBrushToolSettings = other as BrushToolSettings;
- if (otherBrushToolSettings == null) return;
- base.Copy(other);
- _paintOnSurfaceToolSettings.Copy(otherBrushToolSettings._paintOnSurfaceToolSettings);
- _maxHeightFromCenter = otherBrushToolSettings._maxHeightFromCenter;
- _heightType = otherBrushToolSettings._heightType;
- _avoidOverlapping = otherBrushToolSettings._avoidOverlapping;
- _layerFilter = otherBrushToolSettings._layerFilter;
- _tagFilter = otherBrushToolSettings._tagFilter == null ? null
- : new System.Collections.Generic.List<string>(otherBrushToolSettings._tagFilter);
- _slopeFilter = new RandomUtils.Range(otherBrushToolSettings._slopeFilter);
- _terrainLayerFilter = otherBrushToolSettings._terrainLayerFilter == null ? null
- : otherBrushToolSettings._terrainLayerFilter.ToArray();
- _terrainLayerIds = otherBrushToolSettings._terrainLayerIds == null ? null
- : otherBrushToolSettings._terrainLayerIds.ToArray();
- }
- private void UpdateTagFilter()
- {
- if (_tagFilter != null) return;
- _tagFilter = new System.Collections.Generic.List<string>(UnityEditorInternal.InternalEditorUtility.tags);
- }
- private void UpdateTerrainFilter()
- {
- _updateTerrainFilter = false;
- if (_terrainLayerIds == null) return;
- var terrainLayerList = new System.Collections.Generic.List<TerrainLayer>();
- foreach (var globalId in _terrainLayerIds)
- {
- if (UnityEditor.GlobalObjectId.TryParse(globalId, out UnityEditor.GlobalObjectId id))
- {
- var layer = UnityEditor.GlobalObjectId.GlobalObjectIdentifierToObjectSlow(id) as TerrainLayer;
- if (layer == null) continue;
- terrainLayerList.Add(layer);
- }
- }
- _terrainLayerFilter = terrainLayerList.ToArray();
- }
- public void OnBeforeSerialize()
- {
- UpdateTagFilter();
- UpdateTerrainFilter();
- }
- public void OnAfterDeserialize()
- {
- UpdateTagFilter();
- _updateTerrainFilter = true;
- }
- }
- [System.Serializable]
- public class BrushManager : ToolManagerBase<BrushToolSettings> { }
- #endregion
- #region PWBIO
- public static partial class PWBIO
- {
- private static float _brushAngle = 0f;
- private static bool BrushRaycast(Ray ray, out RaycastHit hit, float maxDistance,
- LayerMask layerMask, BrushToolSettings settings, TerrainLayer[] terrainLayers)
- {
- hit = new RaycastHit();
- bool result = false;
- var noColliderDistance = float.MaxValue;
- if (MouseRaycast(ray, out RaycastHit hitInfo, out GameObject collider, maxDistance,
- layerMask, settings.paintOnPalettePrefabs, settings.paintOnMeshesWithoutCollider,
- settings.tagFilter.ToArray(), terrainLayers))
- {
- var nearestRoot = UnityEditor.PrefabUtility.GetNearestPrefabInstanceRoot(collider);
- bool isAPaintedObject = false;
-
- while (nearestRoot != null)
- {
- isAPaintedObject = isAPaintedObject || _paintedObjects.Contains(nearestRoot);
- var parent = nearestRoot.transform.parent == null ? null
- : nearestRoot.transform.parent.gameObject;
- nearestRoot = parent == null ? null : UnityEditor.PrefabUtility.GetNearestPrefabInstanceRoot(parent);
- }
-
- bool selectedOnlyFilter = !settings.paintOnSelectedOnly
- || SelectionManager.selection.Contains(collider)
- || PWBCore.CollidersContains(SelectionManager.selection, collider.name);
-
- bool paletteFilter = !isAPaintedObject || settings.paintOnPalettePrefabs;
- var filterResult = selectedOnlyFilter && paletteFilter;
- result = filterResult;
- if (filterResult && (hitInfo.distance < noColliderDistance))
- hit = hitInfo;
-
- }
- return result;
- }
- private static void BrushDuringSceneGUI(UnityEditor.SceneView sceneView)
- {
- if (BrushManager.settings.paintOnMeshesWithoutCollider)
- PWBCore.CreateTempCollidersWithinFrustum(sceneView.camera);
- BrushstrokeMouseEvents(BrushManager.settings);
- var mousePos = Event.current.mousePosition;
- if (_pinned) mousePos = _pinMouse;
- var mouseRay = UnityEditor.HandleUtility.GUIPointToWorldRay(mousePos);
- bool snappedToVertex = false;
- var closestVertexInfo = new RaycastHit();
- if (_snapToVertex) snappedToVertex = SnapToVertex(mouseRay, out closestVertexInfo, sceneView.in2DMode);
- if (snappedToVertex) mouseRay.origin = closestVertexInfo.point - mouseRay.direction;
- var in2DMode = (PaletteManager.selectedBrush != null && PaletteManager.selectedBrush.isAsset2D)
- && sceneView.in2DMode;
- if (BrushRaycast(mouseRay, out RaycastHit hit, float.MaxValue, -1, BrushManager.settings, null) || in2DMode)
- {
- if (in2DMode)
- {
- hit.point = new Vector3(mouseRay.origin.x, mouseRay.origin.y, 0f);
- hit.normal = Vector3.back;
- }
- DrawBrush(sceneView, ref hit, BrushManager.settings.showPreview);
- }
- else _paintStroke.Clear();
- if (Event.current.button == 0 && !Event.current.alt
- && (Event.current.type == EventType.MouseDown || Event.current.type == EventType.MouseDrag))
- {
- if (!BrushManager.settings.showPreview) DrawBrush(sceneView, ref hit, true);
- Paint(BrushManager.settings);
- Event.current.Use();
- }
- //BrushInfoText(sceneView, hit.point);
- }
- private static void BrushInfoText(UnityEditor.SceneView sceneView, Vector3 hitPoint)
- {
- if (!PWBCore.staticData.showInfoText) return;
- if (_paintStroke.Count == 0) return;
- var labelTexts = new string[]
- { $"{hitPoint.x.ToString("F2")}, {hitPoint.y.ToString("F2")}, {hitPoint.z.ToString("F2")}" };
- InfoText.Draw(sceneView, labelTexts);
- }
- private static Vector3 GetTangent(Vector3 normal)
- {
- var rotation = Quaternion.AngleAxis(_brushAngle, Vector3.up);
- var tangent = Vector3.Cross(normal, rotation * Vector3.right);
- if (tangent.sqrMagnitude < 0.000001) tangent = Vector3.Cross(normal, rotation * Vector3.forward);
- tangent.Normalize();
- return tangent;
- }
- private static void DrawBrush(UnityEditor.SceneView sceneView, ref RaycastHit hit, bool preview)
- {
- var settings = BrushManager.settings;
- UpdateStrokeDirection(hit.point);
- if (PaletteManager.selectedBrush == null) return;
- PWBCore.UpdateTempCollidersIfHierarchyChanged();
- hit.point = SnapAndUpdateGridOrigin(hit.point, SnapManager.settings.snappingEnabled,
- settings.paintOnPalettePrefabs, settings.paintOnMeshesWithoutCollider, false, Vector3.down);
- var tangent = GetTangent(hit.normal);
- var bitangent = Vector3.Cross(hit.normal, tangent);
- if (settings.brushShape == BrushToolSettings.BrushShape.POINT)
- {
- DrawCricleIndicator(hit.point, hit.normal, 0.1f, settings.maxHeightFromCenter,
- tangent, bitangent, hit.normal, settings.paintOnPalettePrefabs, true,
- settings.layerFilter, settings.tagFilter.ToArray());
- }
- else
- {
- UnityEditor.Handles.zTest = UnityEngine.Rendering.CompareFunction.Always;
- UnityEditor.Handles.color = Color.green;
- UnityEditor.Handles.DrawAAPolyLine(3, hit.point, hit.point + hit.normal * settings.maxHeightFromCenter);
- if (settings.brushShape == BrushToolSettings.BrushShape.CIRCLE)
- {
- DrawCricleIndicator(hit.point, hit.normal, settings.radius, settings.maxHeightFromCenter, tangent,
- bitangent, hit.normal, settings.paintOnPalettePrefabs, true,
- settings.layerFilter, settings.tagFilter.ToArray());
- }
- else if (settings.brushShape == BrushToolSettings.BrushShape.SQUARE)
- {
- DrawSquareIndicator(hit.point, hit.normal, settings.radius, settings.maxHeightFromCenter, tangent,
- bitangent, hit.normal, settings.paintOnPalettePrefabs, true,
- settings.layerFilter, settings.tagFilter.ToArray());
- }
- }
- if (preview) BrushstrokePreview(hit.point, hit.normal, tangent, bitangent, sceneView);
- }
- private static void BrushstrokePreview(Vector3 hitPoint, Vector3 normal,
- Vector3 tangent, Vector3 bitangent, UnityEditor.SceneView sceneView)
- {
- var camera = sceneView.camera;
- var settings = BrushManager.settings;
- _paintStroke.Clear();
- var nearbyObjectsAtDensitySpacing = new System.Collections.Generic.List<GameObject>();
- foreach (var strokeItem in BrushstrokeManager.brushstroke)
- {
- var worldPos = hitPoint + TangentSpaceToWorld(tangent, bitangent,
- new Vector2(strokeItem.tangentPosition.x, strokeItem.tangentPosition.y));
- var height = settings.heightType == BrushToolSettings.HeightType.CUSTOM
- ? settings.maxHeightFromCenter : settings.radius;
- var ray = new Ray(worldPos + normal * height, -normal);
- var in2DMode = strokeItem.settings.isAsset2D && sceneView.in2DMode;
-
- if (BrushRaycast(ray, out RaycastHit itemHit, height * 2f, settings.layerFilter,
- settings, settings.terrainLayerFilter) || in2DMode)
- {
- if (in2DMode)
- {
- itemHit.point = new Vector3(worldPos.x, worldPos.y, 0f);
- itemHit.normal = Vector3.forward;
- }
- else
- {
- var slope = Mathf.Abs(Vector3.Angle(Vector3.up, itemHit.normal));
- if (slope > 90f) slope = 180f - slope;
- if (slope < settings.slopeFilter.min || slope > settings.slopeFilter.max) continue;
- }
- var prefab = strokeItem.settings.prefab;
- if (prefab == null) continue;
- BrushSettings brushSettings = strokeItem.settings;
- if (settings.overwriteBrushProperties)
- {
- brushSettings = settings.brushSettings;
- }
- var itemRotation = Quaternion.AngleAxis(_brushAngle, Vector3.up);
- var itemPosition = itemHit.point;
- if (brushSettings.rotateToTheSurface)
- {
- var itemTangent = GetTangent(itemHit.normal);
- itemRotation = Quaternion.LookRotation(itemTangent, itemHit.normal);
- itemPosition += itemHit.normal * brushSettings.surfaceDistance;
- }
- else itemPosition += normal * brushSettings.surfaceDistance;
- if (settings.avoidOverlapping != BrushToolSettings.AvoidOverlappingType.DISABLED
- && settings.avoidOverlapping != BrushToolSettings.AvoidOverlappingType.WITH_ALL_OBJECTS)
- {
- var rSqr = settings.minSpacing * settings.minSpacing;
- var d = settings.density / 100f;
- var densitySpacing = Mathf.Sqrt(rSqr / d) * 0.99999f;
- octree.GetNearbyNonAlloc(itemPosition, densitySpacing, nearbyObjectsAtDensitySpacing);
- if (nearbyObjectsAtDensitySpacing.Count > 0)
- {
- var brushObjectsNearby = false;
- foreach (var obj in nearbyObjectsAtDensitySpacing)
- {
- if (settings.avoidOverlapping
- == BrushToolSettings.AvoidOverlappingType.WITH_BRUSH_PREFABS
- && PaletteManager.selectedBrush.ContainsSceneObject(obj))
- {
- brushObjectsNearby = true;
- break;
- }
- else if (settings.avoidOverlapping
- == BrushToolSettings.AvoidOverlappingType.WITH_PALETTE_PREFABS
- && PaletteManager.selectedPalette.ContainsSceneObject(obj))
- {
- brushObjectsNearby = true;
- break;
- }
- else if (settings.avoidOverlapping
- == BrushToolSettings.AvoidOverlappingType.WITH_SAME_PREFABS)
- {
- var outermostPrefab = UnityEditor.PrefabUtility.GetOutermostPrefabInstanceRoot(obj);
- if (outermostPrefab == null) continue;
- var source = UnityEditor.PrefabUtility.GetCorrespondingObjectFromSource(outermostPrefab);
- if (source == null) continue;
- if (prefab == source)
- {
- brushObjectsNearby = true;
- break;
- }
- }
- }
- if (brushObjectsNearby) continue;
- }
- }
- if (settings.orientAlongBrushstroke)
- {
- itemRotation = Quaternion.Euler(settings.additionalOrientationAngle)
- * Quaternion.LookRotation(_strokeDirection, itemRotation * Vector3.up);
- itemPosition = hitPoint + itemRotation * (itemPosition - hitPoint);
- }
- itemRotation *= Quaternion.Euler(strokeItem.additionalAngle);
- if (brushSettings.alwaysOrientUp)
- {
- var fw = Quaternion.Euler(strokeItem.additionalAngle) * itemHit.normal;
- fw.y = 0;
- const float minMag = 1e-6f;
- if (Mathf.Abs(fw.x) > minMag || Mathf.Abs(fw.z) > minMag)
- itemRotation = Quaternion.LookRotation(fw, Vector3.up);
- }
- itemPosition += itemRotation * brushSettings.localPositionOffset;
- if (brushSettings.embedInSurface && !brushSettings.embedAtPivotHeight)
- {
- var TRS = Matrix4x4.TRS(itemPosition, itemRotation,
- Vector3.Scale(prefab.transform.localScale, strokeItem.scaleMultiplier));
- var localDirection = Quaternion.Inverse(itemRotation) * -normal;
- float magnitudeInDirection;
- var furthestVertices = strokeItem.settings.GetFurthestVerticesInDirection(localDirection,
- out magnitudeInDirection);
- var distanceTosurface = GetDistanceToSurface(furthestVertices, TRS, -normal,
- Mathf.Abs(magnitudeInDirection), BrushManager.settings.paintOnPalettePrefabs,
- BrushManager.settings.paintOnMeshesWithoutCollider, out Transform surfaceTransform, prefab);
- itemPosition -= normal * distanceTosurface;
- }
- var itemScale = Vector3.Scale(prefab.transform.localScale, strokeItem.scaleMultiplier);
- if (settings.avoidOverlapping == BrushToolSettings.AvoidOverlappingType.WITH_ALL_OBJECTS)
- {
- var itemBounds = BoundsUtils.GetBoundsRecursive(prefab.transform, Quaternion.identity);
- var pivotToCenter = itemBounds.center - prefab.transform.position;
- pivotToCenter = Vector3.Scale(pivotToCenter, strokeItem.scaleMultiplier);
- pivotToCenter = itemRotation * pivotToCenter;
- var itemCenter = itemPosition + pivotToCenter;
- var itemHalfExtends = Vector3.Scale(itemBounds.size / 2, strokeItem.scaleMultiplier);
- var overlaped = Physics.OverlapBox(itemCenter, itemHalfExtends,
- itemRotation, -1, QueryTriggerInteraction.Ignore)
- .Where(c => c != itemHit.collider && IsVisible(c.gameObject)).ToArray();
- if (overlaped.Length > 0) continue;
- }
- Transform surface = null;
- GameObject colObj = null;
- if (itemHit.collider != null)
- colObj = PWBCore.GetGameObjectFromTempCollider(itemHit.collider.gameObject);
- if (colObj != null) surface = colObj.transform;
- var layer = settings.overwritePrefabLayer ? settings.layer : prefab.layer;
- Transform parentTransform = GetParent(settings, prefab.name, false, surface);
- _paintStroke.Add(new PaintStrokeItem(prefab, itemPosition,
- itemRotation * Quaternion.Euler(prefab.transform.eulerAngles),
- itemScale, layer, parentTransform, surface, strokeItem.flipX, strokeItem.flipY));
- if (settings.showPreview)
- {
- var rootToWorld = Matrix4x4.TRS(itemPosition, itemRotation, strokeItem.scaleMultiplier)
- * Matrix4x4.Translate(-prefab.transform.position);
- PreviewBrushItem(prefab, rootToWorld, layer, camera, false, false, strokeItem.flipX, strokeItem.flipY);
- }
- }
- }
- }
- }
- #endregion
- }
|