André McGrail
5 年前
当前提交
f083bd65
共有 3452 个文件被更改,包括 11016 次插入 和 0 次删除
-
7Packages/com.unity.render-pipelines.core/.npmignore
-
88Packages/com.unity.render-pipelines.core/CHANGELOG.md
-
7Packages/com.unity.render-pipelines.core/CHANGELOG.md.meta
-
11Packages/com.unity.render-pipelines.core/Documentation~/Camera-Switcher.md
-
16Packages/com.unity.render-pipelines.core/Documentation~/Free-Camera.md
-
11Packages/com.unity.render-pipelines.core/Documentation~/Images/CameraSwitcher1.png
-
19Packages/com.unity.render-pipelines.core/Documentation~/Images/FreeCamera1.png
-
4Packages/com.unity.render-pipelines.core/Documentation~/TableOfContents.md
-
13Packages/com.unity.render-pipelines.core/Documentation~/index.md
-
9Packages/com.unity.render-pipelines.core/Editor.meta
-
100Packages/com.unity.render-pipelines.core/Editor/CameraEditorUtils.cs
-
11Packages/com.unity.render-pipelines.core/Editor/CameraEditorUtils.cs.meta
-
449Packages/com.unity.render-pipelines.core/Editor/CoreEditorDrawers.cs
-
11Packages/com.unity.render-pipelines.core/Editor/CoreEditorDrawers.cs.meta
-
43Packages/com.unity.render-pipelines.core/Editor/CoreEditorStyles.cs
-
11Packages/com.unity.render-pipelines.core/Editor/CoreEditorStyles.cs.meta
-
774Packages/com.unity.render-pipelines.core/Editor/CoreEditorUtils.cs
-
13Packages/com.unity.render-pipelines.core/Editor/CoreEditorUtils.cs.meta
-
8Packages/com.unity.render-pipelines.core/Editor/Debugging.meta
-
106Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugState.cs
-
11Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugState.cs.meta
-
457Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.Builtins.cs
-
11Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.Builtins.cs.meta
-
63Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.cs
-
11Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIDrawer.cs.meta
-
79Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIHandlerCanvasEditor.cs
-
11Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugUIHandlerCanvasEditor.cs.meta
-
580Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs
-
11Packages/com.unity.render-pipelines.core/Editor/Debugging/DebugWindow.cs.meta
-
34Packages/com.unity.render-pipelines.core/Editor/Debugging/UIFoldoutEditor.cs
-
11Packages/com.unity.render-pipelines.core/Editor/Debugging/UIFoldoutEditor.cs.meta
-
46Packages/com.unity.render-pipelines.core/Editor/EditorPrefBoolFlags.cs
-
11Packages/com.unity.render-pipelines.core/Editor/EditorPrefBoolFlags.cs.meta
-
58Packages/com.unity.render-pipelines.core/Editor/ExpandedState.cs
-
11Packages/com.unity.render-pipelines.core/Editor/ExpandedState.cs.meta
-
696Packages/com.unity.render-pipelines.core/Editor/FilterWindow.cs
-
11Packages/com.unity.render-pipelines.core/Editor/FilterWindow.cs.meta
-
8Packages/com.unity.render-pipelines.core/Editor/Gizmo.meta
-
397Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalBox.cs
-
11Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalBox.cs.meta
-
175Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalSphere.cs
-
11Packages/com.unity.render-pipelines.core/Editor/Gizmo/HierarchicalSphere.cs.meta
-
20Packages/com.unity.render-pipelines.core/Editor/Gizmo/UnlitGizmo.shader
-
9Packages/com.unity.render-pipelines.core/Editor/Gizmo/UnlitGizmo.shader.meta
-
814Packages/com.unity.render-pipelines.core/Editor/InspectorCurveEditor.cs
-
11Packages/com.unity.render-pipelines.core/Editor/InspectorCurveEditor.cs.meta
-
8Packages/com.unity.render-pipelines.core/Editor/Lighting.meta
-
626Packages/com.unity.render-pipelines.core/Editor/Lighting/CoreLightEditorUtilities.cs
-
11Packages/com.unity.render-pipelines.core/Editor/Lighting/CoreLightEditorUtilities.cs.meta
-
8Packages/com.unity.render-pipelines.core/Editor/LookDev.meta
-
493Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraController.cs
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraController.cs.meta
-
117Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraState.cs
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/CameraState.cs.meta
-
240Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoController.cs
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoController.cs.meta
-
117Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoState.cs
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/ComparisonGizmoState.cs.meta
-
401Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.cs
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.cs.meta
-
457Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.shader
-
9Packages/com.unity.render-pipelines.core/Editor/LookDev/Compositor.shader.meta
-
400Packages/com.unity.render-pipelines.core/Editor/LookDev/Context.cs
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/Context.cs.meta
-
133Packages/com.unity.render-pipelines.core/Editor/LookDev/CubeToLatlong.shader
-
9Packages/com.unity.render-pipelines.core/Editor/LookDev/CubeToLatlong.shader.meta
-
34Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow-PersonalSkin.uss
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow-PersonalSkin.uss.meta
-
958Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.cs
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.cs.meta
-
325Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.uss
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.uss.meta
-
103Packages/com.unity.render-pipelines.core/Editor/LookDev/DropArea.cs
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/DropArea.cs.meta
-
542Packages/com.unity.render-pipelines.core/Editor/LookDev/Environment.cs
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/Environment.cs.meta
-
198Packages/com.unity.render-pipelines.core/Editor/LookDev/EnvironmentLibrary.cs
-
11Packages/com.unity.render-pipelines.core/Editor/LookDev/EnvironmentLibrary.cs.meta
-
8Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons.meta
-
6Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLight.png
-
77Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLight.png.meta
-
4Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLightl@2x.png
-
77Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevCenterLightl@2x.png.meta
-
3Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose.png
-
77Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose.png.meta
-
4Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose@2x.png
-
77Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevClose@2x.png.meta
-
6Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevEnvRotation.png
-
77Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/LookDevEnvRotation.png.meta
|
|||
sub-package.* |
|||
upm-ci~/** |
|||
.Editor/** |
|||
.yamato/** |
|||
*.zip* |
|||
TestRunnerOptions.json |
|||
.idea/** |
|
|||
# Changelog |
|||
All notable changes to this package will be documented in this file. |
|||
|
|||
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) |
|||
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). |
|||
|
|||
## [7.1.1] - 2019-XX-XX |
|||
|
|||
## [7.0.1] - 2019-07-25 |
|||
|
|||
### Fixed |
|||
- Fixed a precision issue with the ACES tonemapper on mobile platforms. |
|||
|
|||
## [7.0.0] - 2019-07-17 |
|||
|
|||
### Added |
|||
- First experimental version of the LookDev. Works with all SRP. Only branched on HDRP at the moment. |
|||
- LookDev out of experimental |
|||
|
|||
## [6.7.0-preview] - 2019-05-16 |
|||
|
|||
## [6.6.0] - 2019-04-01 |
|||
### Fixed |
|||
- Fixed compile errors in XRGraphics.cs when ENABLE_VR is not defined |
|||
|
|||
## [6.5.0] - 2019-03-07 |
|||
|
|||
## [6.4.0] - 2019-02-21 |
|||
### Added |
|||
- Enabled support for CBUFFER on OpenGL Core and OpenGL ES 3 backends. |
|||
|
|||
## [6.3.0] - 2019-02-18 |
|||
|
|||
## [6.2.0] - 2019-02-15 |
|||
|
|||
## [6.1.0] - 2019-02-13 |
|||
|
|||
## [6.0.0] - 2019-02-23 |
|||
### Fixed |
|||
- Fixed a typo in ERROR_ON_UNSUPPORTED_FUNCTION() that was causing the shader compiler to run out of memory in GLES2. [Case 1104271] (https://issuetracker.unity3d.com/issues/mobile-os-restarts-because-of-high-memory-usage-when-compiling-shaders-for-opengles2) |
|||
|
|||
## [5.2.0] - 2018-11-27 |
|||
|
|||
## [5.1.0] - 2018-11-19 |
|||
### Added |
|||
- Added a define for determining if any instancing path is taken. |
|||
|
|||
### Changed |
|||
- The Core SRP package is no longer in preview. |
|||
|
|||
## [5.0.0-preview] - 2018-10-18 |
|||
### Changed |
|||
- XRGraphicConfig has been changed from a read-write control of XRSettings to XRGraphics, a read-only accessor to XRSettings. This improves consistency of XR behavior between the legacy render pipeline and SRP. |
|||
- XRGraphics members have been renamed to match XRSettings, and XRGraphics has been modified to only contain accessors potentially useful to SRP |
|||
- You can now have up to 16 additional shadow-casting lights. |
|||
### Fixed |
|||
- LWRP no longer executes shadow passes when there are no visible shadow casters in a Scene. Previously, this made the Scene render as too dark, overall. |
|||
|
|||
|
|||
## [4.0.0-preview] - 2018-09-28 |
|||
### Added |
|||
- Space transform functions are now defined in `ShaderLibrary/SpaceTransforms.hlsl`. |
|||
### Changed |
|||
- Removed setting shader inclue path via old API, use package shader include paths |
|||
|
|||
## [3.3.0] |
|||
|
|||
## [3.2.0] |
|||
|
|||
## [3.1.0] |
|||
|
|||
### Added |
|||
- Add PCSS shadow filter |
|||
- Added Core EditMode tests |
|||
- Added Core unsafe utilities |
|||
|
|||
### Improvements |
|||
- Improved volume UI & styling |
|||
- Fixed CoreUtils.QuickSort infinite loop when two elements in the list are equals. |
|||
|
|||
### Changed |
|||
- Moved root files into folders for easier maintenance |
|||
|
|||
## [0.1.6] - 2018-xx-yy |
|||
|
|||
### Changelog starting |
|||
|
|||
Started Changelog |
|
|||
fileFormatVersion: 2 |
|||
guid: 20456a2cc8a214f9d9846725cff9fea4 |
|||
TextScriptImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
# Camera Switcher |
|||
|
|||
The **CameraSwitcher** component allows you to define a List of Cameras in the Scene and then use the Debug Window to switch between them in Play Mode. This is useful when you want a set of different fixed views for profiling purposes where you need to guarantee that the Camera view is in the same position between sessions. |
|||
|
|||
## Properties |
|||
|
|||
![](Images/CameraSwitcher1.png) |
|||
|
|||
| **Property** | **Description** | |
|||
| ------------ | ------------------------------------------------------------ | |
|||
| **Cameras** | Drag and drop GameObjects that have a Camera component attached to add them to this List of Cameras. The Debug Window can switch between the Cameras in this List. | |
|
|||
# Free Camera |
|||
|
|||
The **FreeCamera** component provides you with an implementation for a simple free camera. When you add this component to a Camera, you can use the keyboard and mouse, or a controller, to control the Camera's position and rotation in Play Mode. |
|||
|
|||
## Properties |
|||
|
|||
![](Images/FreeCamera1.png) |
|||
|
|||
| **Property** | **Description** | |
|||
| ------------------------- | ------------------------------------------------------------ | |
|||
| **Look Speed Controller** | Set the look speed of the Camera when using a controller. | |
|||
| **Look Speed Mouse** | Set the look speed of the Camera when using a mouse. | |
|||
| **Move Speed** | Set the speed at which the Camera moves. | |
|||
| **Move Speed Increment** | Set the value of the increment that you can increase or decrease the **Move Speed** by. This is useful if you have a large Scene and the current **Move Speed** is not enough to easily traverse it. | |
|||
| **Turbo** | Set the value that this component multiplies the **Move Speed** by when you press the key or button assigned to "Fire1". | |
|||
|
|
|||
* [Scriptable Render Pipeline Core](index) |
|||
* Camera components |
|||
* [Free Camera](Free-Camera) |
|||
* [Camera Switcher](Camera-Switcher) |
|
|||
# Scriptable Render Pipeline |
|||
![](https://blogs.unity3d.com/wp-content/uploads/2018/01/image5_rs.png) |
|||
## What is the Scriptable Render Pipeline |
|||
|
|||
The Scriptable Render Pipeline (SRP) is a feature that gives you full control over Unity's render pipeline and provides the tools you need to create modern, high-fidelity graphics in Unity. |
|||
|
|||
SRP allows you to write C# scripts to control the way Unity renders each frame. Exposing the render pipeline to you in C# makes Unity less of a “black box” when it comes to rendering. Unlike the original built-in render pipeline, SRP allows you to see and control exactly what happens during the rendering process. |
|||
|
|||
Unity provides you with two prebuilt Scriptable Render Pipelines which you can use in your Projector as a base for your own custom SRP: |
|||
* The Lightweight Render Pipeline (LWRP) offers graphics that scale from mobile platforms to higher-end consoles and PCs. |
|||
* The High Definition Render Pipeline (HDRP) utilizes physically-based lighting techniques to offer high-fidelity graphics to target modern, Compute Shader compatible, platforms. |
|||
|
|||
Rather than developing your own SRP from scratch, you can use either of these prebuilt SRPs as a base to modify and adapt to your own requirements. |
|
|||
fileFormatVersion: 2 |
|||
guid: 74ae8146f4a01491ba1306f3db78139d |
|||
folderAsset: yes |
|||
timeCreated: 1479851675 |
|||
licenseType: Pro |
|||
DefaultImporter: |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
using Object = UnityEngine.Object; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
public static class CameraEditorUtils |
|||
{ |
|||
public delegate Camera GetPreviewCamera(Camera sourceCamera, Vector2 previewSize); |
|||
|
|||
const float k_PreviewNormalizedSize = 0.2f; |
|||
|
|||
internal static Material s_GUITextureBlit2SRGBMaterial; |
|||
public static Material GUITextureBlit2SRGBMaterial |
|||
{ |
|||
get |
|||
{ |
|||
if (!s_GUITextureBlit2SRGBMaterial) |
|||
{ |
|||
Shader shader = EditorGUIUtility.LoadRequired("SceneView/GUITextureBlit2SRGB.shader") as Shader; |
|||
s_GUITextureBlit2SRGBMaterial = new Material(shader); |
|||
s_GUITextureBlit2SRGBMaterial.hideFlags = HideFlags.HideAndDontSave; |
|||
} |
|||
s_GUITextureBlit2SRGBMaterial.SetFloat("_ManualTex2SRGB", QualitySettings.activeColorSpace == ColorSpace.Linear ? 1.0f : 0.0f); |
|||
return s_GUITextureBlit2SRGBMaterial; |
|||
} |
|||
} |
|||
|
|||
public static void DrawCameraSceneViewOverlay(Object target, SceneView sceneView, GetPreviewCamera previewCameraGetter) |
|||
{ |
|||
if (target == null) return; |
|||
|
|||
// cache some deep values
|
|||
var c = (Camera)target; |
|||
|
|||
var previewSize = Handles.GetMainGameViewSize(); |
|||
if (previewSize.x < 0f) |
|||
{ |
|||
// Fallback to Scene View of not a valid game view size
|
|||
previewSize.x = sceneView.position.width; |
|||
previewSize.y = sceneView.position.height; |
|||
} |
|||
// Apply normalizedviewport rect of camera
|
|||
var normalizedViewPortRect = c.rect; |
|||
previewSize.x *= Mathf.Max(normalizedViewPortRect.width, 0f); |
|||
previewSize.y *= Mathf.Max(normalizedViewPortRect.height, 0f); |
|||
|
|||
// Prevent using invalid previewSize
|
|||
if (previewSize.x <= 0f || previewSize.y <= 0f) |
|||
return; |
|||
|
|||
var aspect = previewSize.x / previewSize.y; |
|||
|
|||
// Scale down (fit to scene view)
|
|||
previewSize.y = k_PreviewNormalizedSize * sceneView.position.height; |
|||
previewSize.x = previewSize.y * aspect; |
|||
if (previewSize.y > sceneView.position.height * 0.5f) |
|||
{ |
|||
previewSize.y = sceneView.position.height * 0.5f; |
|||
previewSize.x = previewSize.y * aspect; |
|||
} |
|||
if (previewSize.x > sceneView.position.width * 0.5f) |
|||
{ |
|||
previewSize.x = sceneView.position.width * 0.5f; |
|||
previewSize.y = previewSize.x / aspect; |
|||
} |
|||
|
|||
// Get and reserve rect
|
|||
Rect cameraRect = GUILayoutUtility.GetRect(previewSize.x, previewSize.y); |
|||
|
|||
if (Event.current.type == EventType.Repaint) |
|||
{ |
|||
var previewCamera = previewCameraGetter(c, previewSize); |
|||
if (previewCamera.targetTexture == null) |
|||
{ |
|||
Debug.LogError("The preview camera must render in a render target"); |
|||
return; |
|||
} |
|||
|
|||
sceneView.drawGizmos = false; |
|||
previewCamera.Render(); |
|||
sceneView.drawGizmos = true; |
|||
Graphics.DrawTexture(cameraRect, previewCamera.targetTexture, new Rect(0, 0, 1, 1), 0, 0, 0, 0, GUI.color, GUITextureBlit2SRGBMaterial); |
|||
// We set target texture to null after this call otherwise if both sceneview and gameview are visible and we have a preview camera wwe
|
|||
// get this error: "Releasing render texture that is set as Camera.targetTexture!"
|
|||
previewCamera.targetTexture = null; |
|||
} |
|||
} |
|||
|
|||
public static bool IsViewPortRectValidToRender(Rect normalizedViewPortRect) |
|||
{ |
|||
if (normalizedViewPortRect.width <= 0f || normalizedViewPortRect.height <= 0f) |
|||
return false; |
|||
if (normalizedViewPortRect.x >= 1f || normalizedViewPortRect.xMax <= 0f) |
|||
return false; |
|||
if (normalizedViewPortRect.y >= 1f || normalizedViewPortRect.yMax <= 0f) |
|||
return false; |
|||
return true; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: c9fabbcdc8ab50c44a8112c70c67735c |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
[Flags] |
|||
public enum FoldoutOption |
|||
{ |
|||
None = 0, |
|||
Indent = 1 << 0, |
|||
Boxed = 1 << 2, |
|||
SubFoldout = 1 << 3, |
|||
NoSpaceAtEnd = 1 << 4 |
|||
} |
|||
|
|||
[Flags] |
|||
public enum GroupOption |
|||
{ |
|||
None = 0, |
|||
Indent = 1 << 0 |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Utility class to draw inspectors
|
|||
/// </summary>
|
|||
/// <typeparam name="TData">Type of class containing data needed to draw inspector</typeparam>
|
|||
public static class CoreEditorDrawer<TData> |
|||
{ |
|||
/// <summary> Abstraction that have the Draw hability </summary>
|
|||
public interface IDrawer |
|||
{ |
|||
void Draw(TData p, Editor owner); |
|||
} |
|||
|
|||
public delegate bool Enabler(TData data, Editor owner); |
|||
public delegate void SwitchEnabler(TData data, Editor owner); |
|||
public delegate T2Data DataSelect<T2Data>(TData data, Editor owner); |
|||
public delegate void ActionDrawer(TData data, Editor owner); |
|||
|
|||
/// <summary> Equivalent to EditorGUILayout.Space that can be put in a drawer group </summary>
|
|||
public static readonly IDrawer space = Group((data, owner) => EditorGUILayout.Space()); |
|||
|
|||
/// <summary> Use it when IDrawer required but no operation should be done </summary>
|
|||
public static readonly IDrawer noop = Group((data, owner) => { }); |
|||
|
|||
/// <summary>
|
|||
/// Conditioned drawer that will only be drawn if its enabler function is null or return true
|
|||
/// </summary>
|
|||
/// <param name="enabler">Enable the drawing if null or return true</param>
|
|||
/// <param name="contentDrawers">The content of the group</param>
|
|||
public static IDrawer Conditional(Enabler enabler, params IDrawer[] contentDrawers) |
|||
{ |
|||
return new ConditionalDrawerInternal(enabler, contentDrawers.Draw); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Conditioned drawer that will only be drawn if its enabler function is null or return true
|
|||
/// </summary>
|
|||
/// <param name="enabler">Enable the drawing if null or return true</param>
|
|||
/// <param name="contentDrawers">The content of the group</param>
|
|||
public static IDrawer Conditional(Enabler enabler, params ActionDrawer[] contentDrawers) |
|||
{ |
|||
return new ConditionalDrawerInternal(enabler, contentDrawers); |
|||
} |
|||
|
|||
class ConditionalDrawerInternal : IDrawer |
|||
{ |
|||
ActionDrawer[] actionDrawers { get; set; } |
|||
Enabler m_Enabler; |
|||
|
|||
public ConditionalDrawerInternal(Enabler enabler = null, params ActionDrawer[] actionDrawers) |
|||
{ |
|||
this.actionDrawers = actionDrawers; |
|||
m_Enabler = enabler; |
|||
} |
|||
|
|||
void IDrawer.Draw(TData data, Editor owner) |
|||
{ |
|||
if (m_Enabler != null && !m_Enabler(data, owner)) |
|||
return; |
|||
|
|||
for (var i = 0; i < actionDrawers.Length; i++) |
|||
actionDrawers[i](data, owner); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Group of drawing function for inspector.
|
|||
/// They will be drawn one after the other.
|
|||
/// </summary>
|
|||
/// <param name="contentDrawers">The content of the group</param>
|
|||
public static IDrawer Group(params IDrawer[] contentDrawers) |
|||
{ |
|||
return new GroupDrawerInternal(-1f, GroupOption.None, contentDrawers.Draw); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Group of drawing function for inspector.
|
|||
/// They will be drawn one after the other.
|
|||
/// </summary>
|
|||
/// <param name="contentDrawers">The content of the group</param>
|
|||
public static IDrawer Group(params ActionDrawer[] contentDrawers) |
|||
{ |
|||
return new GroupDrawerInternal(-1f, GroupOption.None, contentDrawers); |
|||
} |
|||
|
|||
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
|
|||
/// <param name="labelWidth">Width used for all labels in the group</param>
|
|||
/// <param name="contentDrawers">The content of the group</param>
|
|||
public static IDrawer Group(float labelWidth, params IDrawer[] contentDrawers) |
|||
{ |
|||
return new GroupDrawerInternal(labelWidth, GroupOption.None, contentDrawers.Draw); |
|||
} |
|||
|
|||
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
|
|||
/// <param name="labelWidth">Width used for all labels in the group</param>
|
|||
/// <param name="contentDrawers">The content of the group</param>
|
|||
public static IDrawer Group(float labelWidth, params ActionDrawer[] contentDrawers) |
|||
{ |
|||
return new GroupDrawerInternal(labelWidth, GroupOption.None, contentDrawers); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Group of drawing function for inspector.
|
|||
/// They will be drawn one after the other.
|
|||
/// </summary>
|
|||
/// <param name="options">Allow to add indentation on this group</param>
|
|||
/// <param name="contentDrawers">The content of the group</param>
|
|||
public static IDrawer Group(GroupOption options, params IDrawer[] contentDrawers) |
|||
{ |
|||
return new GroupDrawerInternal(-1f, options, contentDrawers.Draw); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Group of drawing function for inspector.
|
|||
/// They will be drawn one after the other.
|
|||
/// </summary>
|
|||
/// <param name="options">Allow to add indentation on this group</param>
|
|||
/// <param name="contentDrawers">The content of the group</param>
|
|||
public static IDrawer Group(GroupOption options, params ActionDrawer[] contentDrawers) |
|||
{ |
|||
return new GroupDrawerInternal(-1f, options, contentDrawers); |
|||
} |
|||
|
|||
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
|
|||
/// <param name="labelWidth">Width used for all labels in the group</param>
|
|||
/// <param name="options">Allow to add indentation on this group</param>
|
|||
/// <param name="contentDrawers">The content of the group</param>
|
|||
public static IDrawer Group(float labelWidth, GroupOption options, params IDrawer[] contentDrawers) |
|||
{ |
|||
return new GroupDrawerInternal(labelWidth, options, contentDrawers.Draw); |
|||
} |
|||
|
|||
/// <summary> Group of drawing function for inspector with a set width for labels </summary>
|
|||
/// <param name="labelWidth">Width used for all labels in the group</param>
|
|||
/// <param name="options">Allow to add indentation on this group</param>
|
|||
/// <param name="contentDrawers">The content of the group</param>
|
|||
public static IDrawer Group(float labelWidth, GroupOption options, params ActionDrawer[] contentDrawers) |
|||
{ |
|||
return new GroupDrawerInternal(labelWidth, options, contentDrawers); |
|||
} |
|||
|
|||
class GroupDrawerInternal : IDrawer |
|||
{ |
|||
ActionDrawer[] actionDrawers { get; set; } |
|||
float m_LabelWidth; |
|||
bool isIndented; |
|||
|
|||
public GroupDrawerInternal(float labelWidth = -1f, GroupOption options = GroupOption.None, params ActionDrawer[] actionDrawers) |
|||
{ |
|||
this.actionDrawers = actionDrawers; |
|||
m_LabelWidth = labelWidth; |
|||
isIndented = (options & GroupOption.Indent) != 0; |
|||
} |
|||
|
|||
void IDrawer.Draw(TData data, Editor owner) |
|||
{ |
|||
if (isIndented) |
|||
++EditorGUI.indentLevel; |
|||
var currentLabelWidth = EditorGUIUtility.labelWidth; |
|||
if (m_LabelWidth >= 0f) |
|||
{ |
|||
EditorGUIUtility.labelWidth = m_LabelWidth; |
|||
} |
|||
for (var i = 0; i < actionDrawers.Length; i++) |
|||
actionDrawers[i](data, owner); |
|||
if (m_LabelWidth >= 0f) |
|||
{ |
|||
EditorGUIUtility.labelWidth = currentLabelWidth; |
|||
} |
|||
if (isIndented) |
|||
--EditorGUI.indentLevel; |
|||
} |
|||
} |
|||
|
|||
/// <summary> Create an IDrawer based on an other data container </summary>
|
|||
/// <param name="dataSelect">The data new source for the inner drawers</param>
|
|||
/// <param name="otherDrawers">Inner drawers drawed with given data sources</param>
|
|||
/// <returns></returns>
|
|||
public static IDrawer Select<T2Data>( |
|||
DataSelect<T2Data> dataSelect, |
|||
params CoreEditorDrawer<T2Data>.IDrawer[] otherDrawers) |
|||
{ |
|||
return new SelectDrawerInternal<T2Data>(dataSelect, otherDrawers.Draw); |
|||
} |
|||
|
|||
/// <summary> Create an IDrawer based on an other data container </summary>
|
|||
/// <param name="dataSelect">The data new source for the inner drawers</param>
|
|||
/// <param name="otherDrawers">Inner drawers drawed with given data sources</param>
|
|||
/// <returns></returns>
|
|||
public static IDrawer Select<T2Data>( |
|||
DataSelect<T2Data> dataSelect, |
|||
params CoreEditorDrawer<T2Data>.ActionDrawer[] otherDrawers) |
|||
{ |
|||
return new SelectDrawerInternal<T2Data>(dataSelect, otherDrawers); |
|||
} |
|||
|
|||
class SelectDrawerInternal<T2Data> : IDrawer |
|||
{ |
|||
DataSelect<T2Data> m_DataSelect; |
|||
CoreEditorDrawer<T2Data>.ActionDrawer[] m_SourceDrawers; |
|||
|
|||
public SelectDrawerInternal(DataSelect<T2Data> dataSelect, |
|||
params CoreEditorDrawer<T2Data>.ActionDrawer[] otherDrawers) |
|||
{ |
|||
m_SourceDrawers = otherDrawers; |
|||
m_DataSelect = dataSelect; |
|||
} |
|||
|
|||
void IDrawer.Draw(TData data, Editor o) |
|||
{ |
|||
var p2 = m_DataSelect(data, o); |
|||
for (var i = 0; i < m_SourceDrawers.Length; i++) |
|||
m_SourceDrawers[i](p2, o); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create an IDrawer foldout header using an ExpandedState.
|
|||
/// The default option is Indent in this version.
|
|||
/// </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="contentDrawers">The content of the foldout header</param>
|
|||
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, params IDrawer[] contentDrawers) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return FoldoutGroup(title, mask, state, contentDrawers.Draw); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create an IDrawer foldout header using an ExpandedState.
|
|||
/// The default option is Indent in this version.
|
|||
/// </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="contentDrawers">The content of the foldout header</param>
|
|||
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, params ActionDrawer[] contentDrawers) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return FoldoutGroup(EditorGUIUtility.TrTextContent(title), mask, state, contentDrawers); |
|||
} |
|||
|
|||
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="contentDrawers">The content of the foldout header</param>
|
|||
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params IDrawer[] contentDrawers) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return FoldoutGroup(title, mask, state, options, contentDrawers.Draw); |
|||
} |
|||
|
|||
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="contentDrawers">The content of the foldout header</param>
|
|||
public static IDrawer FoldoutGroup<TEnum, TState>(string title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params ActionDrawer[] contentDrawers) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return FoldoutGroup(EditorGUIUtility.TrTextContent(title), mask, state, options, contentDrawers); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create an IDrawer foldout header using an ExpandedState.
|
|||
/// The default option is Indent in this version.
|
|||
/// </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="contentDrawers">The content of the foldout header</param>
|
|||
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, params IDrawer[] contentDrawers) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return FoldoutGroup(title, mask, state, contentDrawers.Draw); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Create an IDrawer foldout header using an ExpandedState.
|
|||
/// The default option is Indent in this version.
|
|||
/// </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="contentDrawers">The content of the foldout header</param>
|
|||
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, params ActionDrawer[] contentDrawers) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return FoldoutGroup(title, mask, state, FoldoutOption.Indent, contentDrawers); |
|||
} |
|||
|
|||
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="contentDrawers">The content of the foldout header</param>
|
|||
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params IDrawer[] contentDrawers) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return FoldoutGroup(title, mask, state, options, contentDrawers.Draw); |
|||
} |
|||
|
|||
/// <summary> Create an IDrawer foldout header using an ExpandedState </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="contentDrawers">The content of the foldout header</param>
|
|||
public static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, params ActionDrawer[] contentDrawers) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return FoldoutGroup(title, mask, state, options, null, null, contentDrawers); |
|||
} |
|||
|
|||
// This one is private as we do not want to have unhandled advanced switch. Change it if necessary.
|
|||
static IDrawer FoldoutGroup<TEnum, TState>(GUIContent title, TEnum mask, ExpandedState<TEnum, TState> state, FoldoutOption options, Enabler isAdvanced, SwitchEnabler switchAdvanced, params ActionDrawer[] contentDrawers) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return Group((data, owner) => |
|||
{ |
|||
bool isBoxed = (options & FoldoutOption.Boxed) != 0; |
|||
bool isIndented = (options & FoldoutOption.Indent) != 0; |
|||
bool isSubFoldout = (options & FoldoutOption.SubFoldout) != 0; |
|||
bool noSpaceAtEnd = (options & FoldoutOption.NoSpaceAtEnd) != 0; |
|||
bool expended = state[mask]; |
|||
bool newExpended = expended; |
|||
if (isSubFoldout) |
|||
{ |
|||
newExpended = CoreEditorUtils.DrawSubHeaderFoldout(title, expended, isBoxed, |
|||
isAdvanced == null ? (Func<bool>)null : () => isAdvanced(data, owner), |
|||
switchAdvanced == null ? (Action)null : () => switchAdvanced(data, owner)); |
|||
} |
|||
else |
|||
{ |
|||
CoreEditorUtils.DrawSplitter(isBoxed); |
|||
newExpended = CoreEditorUtils.DrawHeaderFoldout(title, expended, isBoxed, |
|||
isAdvanced == null ? (Func<bool>)null : () => isAdvanced(data, owner), |
|||
switchAdvanced == null ? (Action)null : () => switchAdvanced(data, owner)); |
|||
} |
|||
if (newExpended ^ expended) |
|||
state[mask] = newExpended; |
|||
if (newExpended) |
|||
{ |
|||
if (isIndented) |
|||
++EditorGUI.indentLevel; |
|||
for (var i = 0; i < contentDrawers.Length; i++) |
|||
contentDrawers[i](data, owner); |
|||
if (isIndented) |
|||
--EditorGUI.indentLevel; |
|||
if (!noSpaceAtEnd) |
|||
EditorGUILayout.Space(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
|
|||
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
|
|||
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
|
|||
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
|
|||
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, IDrawer normalContent, IDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent.Draw, advancedContent.Draw, options); |
|||
} |
|||
|
|||
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
|
|||
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
|
|||
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
|
|||
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
|
|||
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, ActionDrawer normalContent, IDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent, advancedContent.Draw, options); |
|||
} |
|||
|
|||
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
|
|||
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
|
|||
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
|
|||
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
|
|||
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, IDrawer normalContent, ActionDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return AdvancedFoldoutGroup(foldoutTitle, foldoutMask, foldoutState, isAdvanced, switchAdvanced, normalContent.Draw, advancedContent, options); |
|||
} |
|||
|
|||
/// <summary> Helper to draw a foldout with an advanced switch on it. </summary>
|
|||
/// <param name="title">Title wanted for this foldout header</param>
|
|||
/// <param name="mask">Bit mask (enum) used to define the boolean saving the state in ExpandedState</param>
|
|||
/// <param name="state">The ExpandedState describing the component</param>
|
|||
/// <param name="isAdvanced"> Delegate allowing to check if advanced mode is active. </param>
|
|||
/// <param name="switchAdvanced"> Delegate to know what to do when advance is switched. </param>
|
|||
/// <param name="normalContent"> The content of the foldout header always visible if expended. </param>
|
|||
/// <param name="advancedContent"> The content of the foldout header only visible if advanced mode is active and if foldout is expended. </param>
|
|||
public static IDrawer AdvancedFoldoutGroup<TEnum, TState>(GUIContent foldoutTitle, TEnum foldoutMask, ExpandedState<TEnum, TState> foldoutState, Enabler isAdvanced, SwitchEnabler switchAdvanced, ActionDrawer normalContent, ActionDrawer advancedContent, FoldoutOption options = FoldoutOption.Indent) |
|||
where TEnum : struct, IConvertible |
|||
{ |
|||
return FoldoutGroup(foldoutTitle, foldoutMask, foldoutState, options, isAdvanced, switchAdvanced, |
|||
normalContent, |
|||
Conditional((serialized, owner) => isAdvanced(serialized, owner) && foldoutState[foldoutMask], advancedContent).Draw |
|||
); |
|||
} |
|||
} |
|||
|
|||
public static class CoreEditorDrawersExtensions |
|||
{ |
|||
/// <summary> Concatenate a collection of IDrawer as a unique IDrawer </summary>
|
|||
public static void Draw<TData>(this IEnumerable<CoreEditorDrawer<TData>.IDrawer> drawers, TData data, Editor owner) |
|||
{ |
|||
foreach (var drawer in drawers) |
|||
drawer.Draw(data, owner); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 7e9fe0ff34cd022408422f6b92ad7df6 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
public static class CoreEditorStyles |
|||
{ |
|||
public static readonly GUIStyle smallTickbox; |
|||
public static readonly GUIStyle miniLabelButton; |
|||
|
|||
static readonly Texture2D paneOptionsIconDark; |
|||
static readonly Texture2D paneOptionsIconLight; |
|||
public static Texture2D paneOptionsIcon { get { return EditorGUIUtility.isProSkin ? paneOptionsIconDark : paneOptionsIconLight; } } |
|||
|
|||
static CoreEditorStyles() |
|||
{ |
|||
smallTickbox = new GUIStyle("ShurikenToggle"); |
|||
|
|||
var transparentTexture = new Texture2D(1, 1, TextureFormat.ARGB32, false); |
|||
transparentTexture.SetPixel(0, 0, Color.clear); |
|||
transparentTexture.Apply(); |
|||
|
|||
miniLabelButton = new GUIStyle(EditorStyles.miniLabel); |
|||
miniLabelButton.normal = new GUIStyleState |
|||
{ |
|||
background = transparentTexture, |
|||
scaledBackgrounds = null, |
|||
textColor = Color.grey |
|||
}; |
|||
var activeState = new GUIStyleState |
|||
{ |
|||
background = transparentTexture, |
|||
scaledBackgrounds = null, |
|||
textColor = Color.white |
|||
}; |
|||
miniLabelButton.active = activeState; |
|||
miniLabelButton.onNormal = activeState; |
|||
miniLabelButton.onActive = activeState; |
|||
|
|||
paneOptionsIconDark = (Texture2D)EditorGUIUtility.Load("Builtin Skins/DarkSkin/Images/pane options.png"); |
|||
paneOptionsIconLight = (Texture2D)EditorGUIUtility.Load("Builtin Skins/LightSkin/Images/pane options.png"); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 106fd77ef6b30234597e56c849578bad |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.IO; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Linq.Expressions; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
using UnityEngine; |
|||
using UnityEngine.Rendering; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
using UnityObject = UnityEngine.Object; |
|||
|
|||
public static class CoreEditorUtils |
|||
{ |
|||
class Styles |
|||
{ |
|||
static readonly Color k_Normal_AllTheme = new Color32(0, 0, 0, 0); |
|||
//static readonly Color k_Hover_Dark = new Color32(70, 70, 70, 255);
|
|||
//static readonly Color k_Hover = new Color32(193, 193, 193, 255);
|
|||
static readonly Color k_Active_Dark = new Color32(80, 80, 80, 255); |
|||
static readonly Color k_Active = new Color32(216, 216, 216, 255); |
|||
|
|||
static readonly int s_MoreOptionsHash = "MoreOptions".GetHashCode(); |
|||
|
|||
static public GUIContent moreOptionsLabel { get; private set; } |
|||
static public GUIStyle moreOptionsStyle { get; private set; } |
|||
static public GUIStyle moreOptionsLabelStyle { get; private set; } |
|||
|
|||
static Styles() |
|||
{ |
|||
moreOptionsLabel = EditorGUIUtility.TrIconContent("MoreOptions", "More Options"); |
|||
|
|||
moreOptionsStyle = new GUIStyle(GUI.skin.toggle); |
|||
Texture2D normalColor = new Texture2D(1, 1); |
|||
normalColor.SetPixel(1, 1, k_Normal_AllTheme); |
|||
moreOptionsStyle.normal.background = normalColor; |
|||
moreOptionsStyle.onActive.background = normalColor; |
|||
moreOptionsStyle.onFocused.background = normalColor; |
|||
moreOptionsStyle.onNormal.background = normalColor; |
|||
moreOptionsStyle.onHover.background = normalColor; |
|||
moreOptionsStyle.active.background = normalColor; |
|||
moreOptionsStyle.focused.background = normalColor; |
|||
moreOptionsStyle.hover.background = normalColor; |
|||
|
|||
moreOptionsLabelStyle = new GUIStyle(GUI.skin.label); |
|||
moreOptionsLabelStyle.padding = new RectOffset(0, 0, 0, -1); |
|||
} |
|||
|
|||
//Note:
|
|||
// - GUIStyle seams to be broken: all states have same state than normal light theme
|
|||
// - Hover with event will not be updated right when we enter the rect
|
|||
//-> Removing hover for now. Keep theme color for refactoring with UIElement later
|
|||
static public bool DrawMoreOptions(Rect rect, bool active) |
|||
{ |
|||
int id = GUIUtility.GetControlID(s_MoreOptionsHash, FocusType.Passive, rect); |
|||
var evt = Event.current; |
|||
switch (evt.type) |
|||
{ |
|||
case EventType.Repaint: |
|||
Color background = k_Normal_AllTheme; |
|||
if (active) |
|||
background = EditorGUIUtility.isProSkin ? k_Active_Dark : k_Active; |
|||
EditorGUI.DrawRect(rect, background); |
|||
GUI.Label(rect, moreOptionsLabel, moreOptionsLabelStyle); |
|||
break; |
|||
case EventType.KeyDown: |
|||
bool anyModifiers = (evt.alt || evt.shift || evt.command || evt.control); |
|||
if ((evt.keyCode == KeyCode.Space || evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter) && !anyModifiers && GUIUtility.keyboardControl == id) |
|||
{ |
|||
evt.Use(); |
|||
GUI.changed = true; |
|||
return !active; |
|||
} |
|||
break; |
|||
case EventType.MouseDown: |
|||
if (rect.Contains(evt.mousePosition)) |
|||
{ |
|||
GrabMouseControl(id); |
|||
evt.Use(); |
|||
} |
|||
break; |
|||
case EventType.MouseUp: |
|||
if (HasMouseControl(id)) |
|||
{ |
|||
ReleaseMouseControl(); |
|||
evt.Use(); |
|||
if (rect.Contains(evt.mousePosition)) |
|||
{ |
|||
GUI.changed = true; |
|||
return !active; |
|||
} |
|||
} |
|||
break; |
|||
case EventType.MouseDrag: |
|||
if (HasMouseControl(id)) |
|||
evt.Use(); |
|||
break; |
|||
} |
|||
|
|||
return active; |
|||
} |
|||
|
|||
static int s_GrabbedID = -1; |
|||
static void GrabMouseControl(int id) => s_GrabbedID = id; |
|||
static void ReleaseMouseControl() => s_GrabbedID = -1; |
|||
static bool HasMouseControl(int id) => s_GrabbedID == id; |
|||
} |
|||
|
|||
static GraphicsDeviceType[] m_BuildTargets; |
|||
public static GraphicsDeviceType[] buildTargets => m_BuildTargets ?? (m_BuildTargets = PlayerSettings.GetGraphicsAPIs(EditorUserBuildSettings.activeBuildTarget)); |
|||
|
|||
static CoreEditorUtils() |
|||
{ |
|||
LoadSkinAndIconMethods(); |
|||
} |
|||
|
|||
// Serialization helpers
|
|||
/// <summary>
|
|||
/// To use with extreme caution. It not really get the property but try to find a field with similar name
|
|||
/// Hence inheritance override of property is not supported.
|
|||
/// Also variable rename will silently break the search.
|
|||
/// </summary>
|
|||
public static string FindProperty<T, TValue>(Expression<Func<T, TValue>> expr) |
|||
{ |
|||
// Get the field path as a string
|
|||
MemberExpression me; |
|||
switch (expr.Body.NodeType) |
|||
{ |
|||
case ExpressionType.MemberAccess: |
|||
me = expr.Body as MemberExpression; |
|||
break; |
|||
default: |
|||
throw new InvalidOperationException(); |
|||
} |
|||
|
|||
var members = new List<string>(); |
|||
while (me != null) |
|||
{ |
|||
// For field, get the field name
|
|||
// For properties, get the name of the backing field
|
|||
var name = me.Member is FieldInfo |
|||
? me.Member.Name |
|||
: "m_" + me.Member.Name.Substring(0, 1).ToUpper() + me.Member.Name.Substring(1); |
|||
members.Add(name); |
|||
me = me.Expression as MemberExpression; |
|||
} |
|||
|
|||
var sb = new StringBuilder(); |
|||
for (int i = members.Count - 1; i >= 0; i--) |
|||
{ |
|||
sb.Append(members[i]); |
|||
if (i > 0) sb.Append('.'); |
|||
} |
|||
|
|||
return sb.ToString(); |
|||
} |
|||
|
|||
// UI Helpers
|
|||
public static void DrawFixMeBox(string text, Action action) |
|||
{ |
|||
EditorGUILayout.HelpBox(text, MessageType.Warning); |
|||
|
|||
GUILayout.Space(-32); |
|||
using (new EditorGUILayout.HorizontalScope()) |
|||
{ |
|||
GUILayout.FlexibleSpace(); |
|||
|
|||
if (GUILayout.Button("Fix", GUILayout.Width(60))) |
|||
action(); |
|||
|
|||
GUILayout.Space(8); |
|||
} |
|||
GUILayout.Space(11); |
|||
} |
|||
|
|||
public static void DrawMultipleFields(string label, SerializedProperty[] ppts, GUIContent[] lbls) |
|||
=> DrawMultipleFields(EditorGUIUtility.TrTextContent(label), ppts, lbls); |
|||
|
|||
public static void DrawMultipleFields(GUIContent label, SerializedProperty[] ppts, GUIContent[] lbls) |
|||
{ |
|||
var labelWidth = EditorGUIUtility.labelWidth; |
|||
|
|||
using (new EditorGUILayout.HorizontalScope()) |
|||
{ |
|||
EditorGUILayout.PrefixLabel(label); |
|||
|
|||
using (new EditorGUILayout.VerticalScope()) |
|||
{ |
|||
EditorGUIUtility.labelWidth = 40; |
|||
EditorGUI.indentLevel--; |
|||
for (var i = 0; i < ppts.Length; ++i) |
|||
EditorGUILayout.PropertyField(ppts[i], lbls[i]); |
|||
EditorGUI.indentLevel++; |
|||
} |
|||
} |
|||
|
|||
EditorGUIUtility.labelWidth = labelWidth; |
|||
} |
|||
|
|||
public static void DrawSplitter(bool isBoxed = false) |
|||
{ |
|||
var rect = GUILayoutUtility.GetRect(1f, 1f); |
|||
|
|||
// Splitter rect should be full-width
|
|||
rect.xMin = 0f; |
|||
rect.width += 4f; |
|||
|
|||
if (isBoxed) |
|||
{ |
|||
rect.xMin = EditorGUIUtility.singleLineHeight - 2; |
|||
rect.width -= 1; |
|||
} |
|||
|
|||
if (Event.current.type != EventType.Repaint) |
|||
return; |
|||
|
|||
EditorGUI.DrawRect(rect, !EditorGUIUtility.isProSkin |
|||
? new Color(0.6f, 0.6f, 0.6f, 1.333f) |
|||
: new Color(0.12f, 0.12f, 0.12f, 1.333f)); |
|||
} |
|||
|
|||
public static void DrawHeader(string title) |
|||
=> DrawHeader(EditorGUIUtility.TrTextContent(title)); |
|||
|
|||
public static void DrawHeader(GUIContent title) |
|||
{ |
|||
var backgroundRect = GUILayoutUtility.GetRect(1f, 17f); |
|||
|
|||
var labelRect = backgroundRect; |
|||
labelRect.xMin += 16f; |
|||
labelRect.xMax -= 20f; |
|||
|
|||
var foldoutRect = backgroundRect; |
|||
foldoutRect.y += 1f; |
|||
foldoutRect.width = 13f; |
|||
foldoutRect.height = 13f; |
|||
|
|||
// Background rect should be full-width
|
|||
backgroundRect.xMin = 0f; |
|||
backgroundRect.width += 4f; |
|||
|
|||
// Background
|
|||
float backgroundTint = EditorGUIUtility.isProSkin ? 0.1f : 1f; |
|||
EditorGUI.DrawRect(backgroundRect, new Color(backgroundTint, backgroundTint, backgroundTint, 0.2f)); |
|||
|
|||
// Title
|
|||
EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel); |
|||
} |
|||
|
|||
/// <summary> Draw a foldout header </summary>
|
|||
/// <param name="title"> The title of the header </param>
|
|||
/// <param name="state"> The state of the header </param>
|
|||
/// <param name="isBoxed"> [optional] is the eader contained in a box style ? </param>
|
|||
/// <param name="hasMoreOptions"> [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. </param>
|
|||
/// <param name="toggleMoreOption"> [optional] Callback call when advanced button clicked. Should be used to toggle its state. </param>
|
|||
public static bool DrawHeaderFoldout(string title, bool state, bool isBoxed = false, Func<bool> hasMoreOptions = null, Action toggleMoreOption = null) |
|||
=> DrawHeaderFoldout(EditorGUIUtility.TrTextContent(title), state, isBoxed, hasMoreOptions, toggleMoreOption); |
|||
|
|||
/// <summary> Draw a foldout header </summary>
|
|||
/// <param name="title"> The title of the header </param>
|
|||
/// <param name="state"> The state of the header </param>
|
|||
/// <param name="isBoxed"> [optional] is the eader contained in a box style ? </param>
|
|||
/// <param name="hasMoreOptions"> [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. </param>
|
|||
/// <param name="toggleMoreOptions"> [optional] Callback call when advanced button clicked. Should be used to toggle its state. </param>
|
|||
public static bool DrawHeaderFoldout(GUIContent title, bool state, bool isBoxed = false, Func<bool> hasMoreOptions = null, Action toggleMoreOptions = null) |
|||
{ |
|||
const float height = 17f; |
|||
var backgroundRect = GUILayoutUtility.GetRect(1f, height); |
|||
|
|||
var labelRect = backgroundRect; |
|||
labelRect.xMin += 16f; |
|||
labelRect.xMax -= 20f; |
|||
|
|||
var foldoutRect = backgroundRect; |
|||
foldoutRect.y += 1f; |
|||
foldoutRect.width = 13f; |
|||
foldoutRect.height = 13f; |
|||
|
|||
// More options 1/2
|
|||
var moreOptionsRect = new Rect(); |
|||
if (hasMoreOptions != null) |
|||
{ |
|||
moreOptionsRect = backgroundRect; |
|||
moreOptionsRect.x += moreOptionsRect.width - 16 - 1; |
|||
moreOptionsRect.height = 15; |
|||
moreOptionsRect.width = 16; |
|||
} |
|||
|
|||
// Background rect should be full-width
|
|||
backgroundRect.xMin = 0f; |
|||
backgroundRect.width += 4f; |
|||
|
|||
if (isBoxed) |
|||
{ |
|||
labelRect.xMin += 5; |
|||
foldoutRect.xMin += 5; |
|||
backgroundRect.xMin = EditorGUIUtility.singleLineHeight; |
|||
backgroundRect.width -= 3; |
|||
} |
|||
|
|||
// Background
|
|||
float backgroundTint = EditorGUIUtility.isProSkin ? 0.1f : 1f; |
|||
EditorGUI.DrawRect(backgroundRect, new Color(backgroundTint, backgroundTint, backgroundTint, 0.2f)); |
|||
|
|||
// More options 2/2
|
|||
if (hasMoreOptions != null) |
|||
{ |
|||
EditorGUI.BeginChangeCheck(); |
|||
Styles.DrawMoreOptions(moreOptionsRect, hasMoreOptions()); |
|||
if (EditorGUI.EndChangeCheck() && toggleMoreOptions != null) |
|||
{ |
|||
toggleMoreOptions(); |
|||
} |
|||
} |
|||
|
|||
// Title
|
|||
EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel); |
|||
|
|||
// Active checkbox
|
|||
state = GUI.Toggle(foldoutRect, state, GUIContent.none, EditorStyles.foldout); |
|||
|
|||
var e = Event.current; |
|||
if (e.type == EventType.MouseDown && backgroundRect.Contains(e.mousePosition) && !moreOptionsRect.Contains(e.mousePosition) && e.button == 0) |
|||
{ |
|||
state = !state; |
|||
e.Use(); |
|||
} |
|||
|
|||
return state; |
|||
} |
|||
|
|||
/// <summary> Draw a foldout header </summary>
|
|||
/// <param name="title"> The title of the header </param>
|
|||
/// <param name="state"> The state of the header </param>
|
|||
/// <param name="isBoxed"> [optional] is the eader contained in a box style ? </param>
|
|||
/// <param name="hasMoreOption"> [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. </param>
|
|||
/// <param name="toggleMoreOptions"> [optional] Callback call when advanced button clicked. Should be used to toggle its state. </param>
|
|||
public static bool DrawSubHeaderFoldout(string title, bool state, bool isBoxed = false, Func<bool> hasMoreOptions = null, Action toggleMoreOptions = null) |
|||
=> DrawSubHeaderFoldout(EditorGUIUtility.TrTextContent(title), state, isBoxed, hasMoreOptions, toggleMoreOptions); |
|||
|
|||
/// <summary> Draw a foldout header </summary>
|
|||
/// <param name="title"> The title of the header </param>
|
|||
/// <param name="state"> The state of the header </param>
|
|||
/// <param name="isBoxed"> [optional] is the eader contained in a box style ? </param>
|
|||
/// <param name="hasMoreOptions"> [optional] Delegate used to draw the right state of the advanced button. If null, no button drawn. </param>
|
|||
/// <param name="toggleMoreOptions"> [optional] Callback call when advanced button clicked. Should be used to toggle its state. </param>
|
|||
public static bool DrawSubHeaderFoldout(GUIContent title, bool state, bool isBoxed = false, Func<bool> hasMoreOptions = null, Action toggleMoreOptions = null) |
|||
{ |
|||
const float height = 17f; |
|||
var backgroundRect = GUILayoutUtility.GetRect(1f, height); |
|||
|
|||
var labelRect = backgroundRect; |
|||
labelRect.xMin += 16f; |
|||
labelRect.xMax -= 20f; |
|||
|
|||
var foldoutRect = backgroundRect; |
|||
foldoutRect.y += 1f; |
|||
foldoutRect.x += 15 * EditorGUI.indentLevel; //GUI do not handle indent. Handle it here
|
|||
foldoutRect.width = 13f; |
|||
foldoutRect.height = 13f; |
|||
|
|||
// More options
|
|||
var advancedRect = new Rect(); |
|||
if (hasMoreOptions != null) |
|||
{ |
|||
advancedRect = backgroundRect; |
|||
advancedRect.x += advancedRect.width - 16 - 1; |
|||
advancedRect.height = 16; |
|||
advancedRect.width = 16; |
|||
|
|||
bool moreOptions = hasMoreOptions(); |
|||
bool newMoreOptions = Styles.DrawMoreOptions(advancedRect, moreOptions); |
|||
if (moreOptions ^ newMoreOptions) |
|||
toggleMoreOptions?.Invoke(); |
|||
} |
|||
|
|||
// Background rect should be full-width
|
|||
backgroundRect.xMin = 0f; |
|||
backgroundRect.width += 4f; |
|||
|
|||
if (isBoxed) |
|||
{ |
|||
labelRect.xMin += 5; |
|||
foldoutRect.xMin += 5; |
|||
backgroundRect.xMin = EditorGUIUtility.singleLineHeight; |
|||
backgroundRect.width -= 3; |
|||
} |
|||
|
|||
// Title
|
|||
EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel); |
|||
|
|||
// Active checkbox
|
|||
state = GUI.Toggle(foldoutRect, state, GUIContent.none, EditorStyles.foldout); |
|||
|
|||
var e = Event.current; |
|||
if (e.type == EventType.MouseDown && backgroundRect.Contains(e.mousePosition) && !advancedRect.Contains(e.mousePosition) && e.button == 0) |
|||
{ |
|||
state = !state; |
|||
e.Use(); |
|||
} |
|||
|
|||
return state; |
|||
} |
|||
|
|||
public static bool DrawHeaderToggle(string title, SerializedProperty group, SerializedProperty activeField, Action<Vector2> contextAction = null, Func<bool> hasMoreOptions = null, Action toggleMoreOptions = null) |
|||
=> DrawHeaderToggle(EditorGUIUtility.TrTextContent(title), group, activeField, contextAction, hasMoreOptions, toggleMoreOptions); |
|||
|
|||
public static bool DrawHeaderToggle(GUIContent title, SerializedProperty group, SerializedProperty activeField, Action<Vector2> contextAction = null, Func<bool> hasMoreOptions = null, Action toggleMoreOptions = null) |
|||
{ |
|||
var backgroundRect = GUILayoutUtility.GetRect(1f, 17f); |
|||
|
|||
var labelRect = backgroundRect; |
|||
labelRect.xMin += 32f; |
|||
labelRect.xMax -= 20f + 16 + 5; |
|||
|
|||
var foldoutRect = backgroundRect; |
|||
foldoutRect.y += 1f; |
|||
foldoutRect.width = 13f; |
|||
foldoutRect.height = 13f; |
|||
|
|||
var toggleRect = backgroundRect; |
|||
toggleRect.x += 16f; |
|||
toggleRect.y += 2f; |
|||
toggleRect.width = 13f; |
|||
toggleRect.height = 13f; |
|||
|
|||
// More options 1/2
|
|||
var moreOptionsRect = new Rect(); |
|||
if (hasMoreOptions != null) |
|||
{ |
|||
moreOptionsRect = backgroundRect; |
|||
moreOptionsRect.x += moreOptionsRect.width - 16 - 1 - 16 - 5; |
|||
moreOptionsRect.height = 15; |
|||
moreOptionsRect.width = 16; |
|||
} |
|||
|
|||
// Background rect should be full-width
|
|||
backgroundRect.xMin = 0f; |
|||
backgroundRect.width += 4f; |
|||
|
|||
// Background
|
|||
float backgroundTint = EditorGUIUtility.isProSkin ? 0.1f : 1f; |
|||
EditorGUI.DrawRect(backgroundRect, new Color(backgroundTint, backgroundTint, backgroundTint, 0.2f)); |
|||
|
|||
// Title
|
|||
using (new EditorGUI.DisabledScope(!activeField.boolValue)) |
|||
EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel); |
|||
|
|||
// Foldout
|
|||
group.serializedObject.Update(); |
|||
group.isExpanded = GUI.Toggle(foldoutRect, group.isExpanded, GUIContent.none, EditorStyles.foldout); |
|||
group.serializedObject.ApplyModifiedProperties(); |
|||
|
|||
// Active checkbox
|
|||
activeField.serializedObject.Update(); |
|||
activeField.boolValue = GUI.Toggle(toggleRect, activeField.boolValue, GUIContent.none, CoreEditorStyles.smallTickbox); |
|||
activeField.serializedObject.ApplyModifiedProperties(); |
|||
|
|||
// More options 2/2
|
|||
if (hasMoreOptions != null) |
|||
{ |
|||
bool moreOptions = hasMoreOptions(); |
|||
bool newMoreOptions = Styles.DrawMoreOptions(moreOptionsRect, moreOptions); |
|||
if (moreOptions ^ newMoreOptions) |
|||
toggleMoreOptions?.Invoke(); |
|||
} |
|||
|
|||
// Context menu
|
|||
var menuIcon = CoreEditorStyles.paneOptionsIcon; |
|||
var menuRect = new Rect(labelRect.xMax + 3f + 16 + 5 , labelRect.y + 1f, menuIcon.width, menuIcon.height); |
|||
|
|||
if (contextAction != null) |
|||
GUI.DrawTexture(menuRect, menuIcon); |
|||
|
|||
// Handle events
|
|||
var e = Event.current; |
|||
|
|||
if (e.type == EventType.MouseDown) |
|||
{ |
|||
if (contextAction != null && menuRect.Contains(e.mousePosition)) |
|||
{ |
|||
contextAction(new Vector2(menuRect.x, menuRect.yMax)); |
|||
e.Use(); |
|||
} |
|||
else if (labelRect.Contains(e.mousePosition)) |
|||
{ |
|||
if (e.button == 0) |
|||
group.isExpanded = !group.isExpanded; |
|||
else if (contextAction != null) |
|||
contextAction(e.mousePosition); |
|||
|
|||
e.Use(); |
|||
} |
|||
} |
|||
|
|||
return group.isExpanded; |
|||
} |
|||
|
|||
static readonly GUIContent[] k_DrawVector6_Label = |
|||
{ |
|||
new GUIContent("X"), |
|||
new GUIContent("Y"), |
|||
new GUIContent("Z"), |
|||
}; |
|||
const int k_DrawVector6Slider_LabelSize = 60; |
|||
const int k_DrawVector6Slider_FieldSize = 80; |
|||
|
|||
public static void DrawVector6(GUIContent label, SerializedProperty positive, SerializedProperty negative, Vector3 min, Vector3 max, Color[] colors = null, SerializedProperty multiplicator = null) |
|||
{ |
|||
if (colors != null && (colors.Length != 6)) |
|||
throw new System.ArgumentException("Colors must be a 6 element array. [+X, +Y, +X, -X, -Y, -Z]"); |
|||
|
|||
GUILayout.BeginVertical(); |
|||
Rect rect = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(0, float.MaxValue, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight)); |
|||
if (label != GUIContent.none) |
|||
{ |
|||
var labelRect = rect; |
|||
labelRect.x -= 15f * EditorGUI.indentLevel; |
|||
labelRect.width = EditorGUIUtility.labelWidth; |
|||
EditorGUI.LabelField(labelRect, label); |
|||
rect.x += EditorGUIUtility.labelWidth - 1f - 15f * EditorGUI.indentLevel; |
|||
rect.width -= EditorGUIUtility.labelWidth - 1f - 15f * EditorGUI.indentLevel; |
|||
} |
|||
DrawVector3(rect, k_DrawVector6_Label, positive, min, max, false, colors == null ? null : new Color[] { colors[0], colors[1], colors[2] }, multiplicator); |
|||
|
|||
GUILayout.Space(EditorGUIUtility.standardVerticalSpacing); |
|||
|
|||
rect = EditorGUI.IndentedRect(GUILayoutUtility.GetRect(0, float.MaxValue, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight)); |
|||
rect.x += EditorGUIUtility.labelWidth - 1f - 15f * EditorGUI.indentLevel; |
|||
rect.width -= EditorGUIUtility.labelWidth - 1f - 15f * EditorGUI.indentLevel; |
|||
DrawVector3(rect, k_DrawVector6_Label, negative, min, max, true, colors == null ? null : new Color[] { colors[3], colors[4], colors[5] }, multiplicator); |
|||
|
|||
GUILayout.EndVertical(); |
|||
} |
|||
|
|||
static void DrawVector3(Rect rect, GUIContent[] labels, SerializedProperty value, Vector3 min, Vector3 max, bool addMinusPrefix, Color[] colors, SerializedProperty multiplicator = null) |
|||
{ |
|||
float[] multifloat = multiplicator == null |
|||
? new float[] { value.vector3Value.x, value.vector3Value.y, value.vector3Value.z } |
|||
: new float[] { value.vector3Value.x * multiplicator.vector3Value.x, value.vector3Value.y * multiplicator.vector3Value.y, value.vector3Value.z * multiplicator.vector3Value.z }; |
|||
|
|||
float fieldWidth = rect.width / 3f; |
|||
EditorGUI.showMixedValue = value.hasMultipleDifferentValues; |
|||
EditorGUI.BeginChangeCheck(); |
|||
EditorGUI.MultiFloatField(rect, labels, multifloat); |
|||
if(EditorGUI.EndChangeCheck()) |
|||
{ |
|||
value.vector3Value = multiplicator == null |
|||
? new Vector3( |
|||
Mathf.Clamp(multifloat[0], min.x, max.x), |
|||
Mathf.Clamp(multifloat[1], min.y, max.y), |
|||
Mathf.Clamp(multifloat[2], min.z, max.z) |
|||
) |
|||
: new Vector3( |
|||
Mathf.Clamp((multiplicator.vector3Value.x < -0.00001 || 0.00001 < multiplicator.vector3Value.x) ? multifloat[0] / multiplicator.vector3Value.x : 0f, min.x, max.x), |
|||
Mathf.Clamp((multiplicator.vector3Value.y < -0.00001 || 0.00001 < multiplicator.vector3Value.y) ? multifloat[1] / multiplicator.vector3Value.y : 0f, min.y, max.y), |
|||
Mathf.Clamp((multiplicator.vector3Value.z < -0.00001 || 0.00001 < multiplicator.vector3Value.z) ? multifloat[2] / multiplicator.vector3Value.z : 0f, min.z, max.z) |
|||
); |
|||
} |
|||
EditorGUI.showMixedValue = false; |
|||
|
|||
//Suffix is a hack as sublabel only work with 1 character
|
|||
if (addMinusPrefix) |
|||
{ |
|||
Rect suffixRect = new Rect(rect.x - 4 - 15 * EditorGUI.indentLevel, rect.y, 100, rect.height); |
|||
for(int i = 0; i < 3; ++i) |
|||
{ |
|||
EditorGUI.LabelField(suffixRect, "-"); |
|||
suffixRect.x += fieldWidth + .66f; |
|||
} |
|||
} |
|||
|
|||
//Color is a hack as nothing is done to handle this at the moment
|
|||
if(colors != null) |
|||
{ |
|||
if (colors.Length != 3) |
|||
throw new System.ArgumentException("colors must have 3 elements."); |
|||
|
|||
Rect suffixRect = new Rect(rect.x + 7 - 15 * EditorGUI.indentLevel, rect.y, 100, rect.height); |
|||
GUIStyle colorMark = new GUIStyle(EditorStyles.label); |
|||
colorMark.normal.textColor = colors[0]; |
|||
EditorGUI.LabelField(suffixRect, "|", colorMark); |
|||
suffixRect.x += 1; |
|||
EditorGUI.LabelField(suffixRect, "|", colorMark); |
|||
suffixRect.x += fieldWidth - .5f; |
|||
colorMark.normal.textColor = colors[1]; |
|||
EditorGUI.LabelField(suffixRect, "|", colorMark); |
|||
suffixRect.x += 1; |
|||
EditorGUI.LabelField(suffixRect, "|", colorMark); |
|||
suffixRect.x += fieldWidth; |
|||
colorMark.normal.textColor = colors[2]; |
|||
EditorGUI.LabelField(suffixRect, "|", colorMark); |
|||
suffixRect.x += 1; |
|||
EditorGUI.LabelField(suffixRect, "|", colorMark); |
|||
} |
|||
} |
|||
|
|||
public static void DrawPopup(GUIContent label, SerializedProperty property, string[] options) |
|||
{ |
|||
var mode = property.intValue; |
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
if (mode >= options.Length) |
|||
Debug.LogError(string.Format("Invalid option while trying to set {0}", label.text)); |
|||
|
|||
mode = EditorGUILayout.Popup(label, mode, options); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
property.intValue = mode; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Draw an EnumPopup handling multiEdition
|
|||
/// </summary>
|
|||
/// <param name="property"></param>
|
|||
/// <param name="type"></param>
|
|||
/// <param name="label"></param>
|
|||
public static void DrawEnumPopup(SerializedProperty property, System.Type type, GUIContent label = null) |
|||
{ |
|||
EditorGUI.showMixedValue = property.hasMultipleDifferentValues; |
|||
EditorGUI.BeginChangeCheck(); |
|||
var name = System.Enum.GetName(type, property.intValue); |
|||
var index = System.Array.FindIndex(System.Enum.GetNames(type), n => n == name); |
|||
var input = (System.Enum)System.Enum.GetValues(type).GetValue(index); |
|||
var rawResult = EditorGUILayout.EnumPopup(label ?? EditorGUIUtility.TrTextContent(ObjectNames.NicifyVariableName(property.name)), input); |
|||
var result = ((System.IConvertible)rawResult).ToInt32(System.Globalization.CultureInfo.CurrentCulture); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
property.intValue = result; |
|||
EditorGUI.showMixedValue = false; |
|||
} |
|||
|
|||
public static void RemoveMaterialKeywords(Material material) |
|||
=> material.shaderKeywords = null; |
|||
|
|||
public static T[] GetAdditionalData<T>(UnityEngine.Object[] targets, Action<T> initDefault = null) |
|||
where T : Component |
|||
{ |
|||
// Handles multi-selection
|
|||
var data = targets.Cast<Component>() |
|||
.Select(t => t.GetComponent<T>()) |
|||
.ToArray(); |
|||
|
|||
for (int i = 0; i < data.Length; i++) |
|||
{ |
|||
if (data[i] == null) |
|||
{ |
|||
data[i] = Undo.AddComponent<T>(((Component)targets[i]).gameObject); |
|||
if (initDefault != null) |
|||
{ |
|||
initDefault(data[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
return data; |
|||
} |
|||
|
|||
static public GameObject CreateGameObject(GameObject parent, string name, params Type[] types) |
|||
=> ObjectFactory.CreateGameObject(GameObjectUtility.GetUniqueNameForSibling(parent != null ? parent.transform : null, name), types); |
|||
|
|||
static public string GetCurrentProjectVersion() |
|||
{ |
|||
string[] readText = File.ReadAllLines("ProjectSettings/ProjectVersion.txt"); |
|||
// format is m_EditorVersion: 2018.2.0b7
|
|||
string[] versionText = readText[0].Split(' '); |
|||
return versionText[1]; |
|||
} |
|||
|
|||
static public void CheckOutFile(bool VCSEnabled, UnityObject mat) |
|||
{ |
|||
if (VCSEnabled) |
|||
{ |
|||
UnityEditor.VersionControl.Task task = UnityEditor.VersionControl.Provider.Checkout(mat, UnityEditor.VersionControl.CheckoutMode.Both); |
|||
|
|||
if (!task.success) |
|||
{ |
|||
Debug.Log(task.text + " " + task.resultCode); |
|||
} |
|||
} |
|||
} |
|||
|
|||
|
|||
#region IconAndSkin
|
|||
|
|||
internal enum Skin |
|||
{ |
|||
Auto, |
|||
Personnal, |
|||
Professional, |
|||
} |
|||
|
|||
static Func<int> GetInternalSkinIndex; |
|||
static Func<float> GetGUIStatePixelsPerPoint; |
|||
static Func<Texture2D, float> GetTexturePixelPerPoint; |
|||
static Action<Texture2D, float> SetTexturePixelPerPoint; |
|||
|
|||
static void LoadSkinAndIconMethods() |
|||
{ |
|||
var internalSkinIndexInfo = typeof(EditorGUIUtility).GetProperty("skinIndex", BindingFlags.NonPublic | BindingFlags.Static); |
|||
var internalSkinIndexLambda = Expression.Lambda<Func<int>>(Expression.Property(null, internalSkinIndexInfo)); |
|||
GetInternalSkinIndex = internalSkinIndexLambda.Compile(); |
|||
|
|||
var guiStatePixelsPerPointInfo = typeof(GUIUtility).GetProperty("pixelsPerPoint", BindingFlags.NonPublic | BindingFlags.Static); |
|||
var guiStatePixelsPerPointLambda = Expression.Lambda<Func<float>>(Expression.Property(null, guiStatePixelsPerPointInfo)); |
|||
GetGUIStatePixelsPerPoint = guiStatePixelsPerPointLambda.Compile(); |
|||
|
|||
var pixelPerPointParam = Expression.Parameter(typeof(float), "pixelPerPoint"); |
|||
var texture2DProperty = Expression.Parameter(typeof(Texture2D), "texture2D"); |
|||
var texture2DPixelsPerPointInfo = typeof(Texture2D).GetProperty("pixelsPerPoint", BindingFlags.NonPublic | BindingFlags.Instance); |
|||
var texture2DPixelsPerPointProperty = Expression.Property(texture2DProperty, texture2DPixelsPerPointInfo); |
|||
var texture2DGetPixelsPerPointLambda = Expression.Lambda<Func<Texture2D, float>>(texture2DPixelsPerPointProperty, texture2DProperty); |
|||
GetTexturePixelPerPoint = texture2DGetPixelsPerPointLambda.Compile(); |
|||
var texture2DSetPixelsPerPointLambda = Expression.Lambda<Action<Texture2D, float>>(Expression.Assign(texture2DPixelsPerPointProperty, pixelPerPointParam), texture2DProperty, pixelPerPointParam); |
|||
SetTexturePixelPerPoint = texture2DSetPixelsPerPointLambda.Compile(); |
|||
} |
|||
|
|||
/// <summary>Get the skin currently in use</summary>
|
|||
static Skin currentSkin |
|||
=> GetInternalSkinIndex() == 0 ? Skin.Personnal : Skin.Professional; |
|||
|
|||
|
|||
// /!\ UIElement do not support well pixel per point at the moment. For this, use the hack forceLowRes
|
|||
/// <summary>
|
|||
/// Load an icon regarding skin and editor resolution.
|
|||
/// Icon should be stored as legacy icon resources:
|
|||
/// - "d_" prefix for Professional theme
|
|||
/// - "@2x" suffix for high resolution
|
|||
/// </summary>
|
|||
/// <param name="path">Path to seek the icon from Assets/ folder</param>
|
|||
/// <param name="name">Icon name without suffix, prefix or extention</param>
|
|||
/// <param name="extention">[Optional] Extention of file (png per default)</param>
|
|||
/// <param name="skin">[Optional] Load icon for this skin (Auto per default take current skin)</param>
|
|||
public static Texture2D LoadIcon(string path, string name, string extention = ".png", bool forceLowRes = false) |
|||
{ |
|||
if (String.IsNullOrEmpty(path) || String.IsNullOrEmpty(name)) |
|||
return null; |
|||
|
|||
string prefix = ""; |
|||
|
|||
var skin = currentSkin; |
|||
if (skin == Skin.Professional) |
|||
prefix = "d_"; |
|||
|
|||
Texture2D icon = null; |
|||
float pixelsPerPoint = GetGUIStatePixelsPerPoint(); |
|||
if (pixelsPerPoint > 1.0f && !forceLowRes) |
|||
{ |
|||
icon = EditorGUIUtility.Load(String.Format("{0}/{1}{2}@2x{3}", path, prefix, name, extention)) as Texture2D; |
|||
if (icon == null && !string.IsNullOrEmpty(prefix)) |
|||
icon = EditorGUIUtility.Load(String.Format("{0}/{1}@2x{2}", path, name, extention)) as Texture2D; |
|||
if (icon != null) |
|||
SetTexturePixelPerPoint(icon, 2.0f); |
|||
} |
|||
|
|||
if (icon == null) |
|||
icon = EditorGUIUtility.Load(String.Format("{0}/{1}{2}{3}", path, prefix, name, extention)) as Texture2D; |
|||
|
|||
if (icon == null && !string.IsNullOrEmpty(prefix)) |
|||
icon = EditorGUIUtility.Load(String.Format("{0}/{1}{2}", path, name, extention)) as Texture2D; |
|||
|
|||
if (icon != null && |
|||
!Mathf.Approximately(GetTexturePixelPerPoint(icon), pixelsPerPoint) && //scaling are different
|
|||
!Mathf.Approximately(pixelsPerPoint % 1, 0)) //screen scaling is non-integer
|
|||
{ |
|||
icon.filterMode = FilterMode.Bilinear; |
|||
} |
|||
|
|||
return icon; |
|||
} |
|||
|
|||
#endregion
|
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 744ceabda269e6c469964dda8c490d0d |
|||
timeCreated: 1507109827 |
|||
licenseType: Pro |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: dca094a63ee8e4df7b043d29c2c100e4 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEngine; |
|||
using UnityEngine.Rendering; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
[Serializable] |
|||
public abstract class DebugState : ScriptableObject |
|||
{ |
|||
[SerializeField] |
|||
protected string m_QueryPath; |
|||
|
|||
// We need this to keep track of the state modified in the current frame.
|
|||
// This helps reduces the cost of re-applying states to original widgets and is also needed
|
|||
// when two states point to the same value (e.g. when using split enums like HDRP does for
|
|||
// the `fullscreenDebugMode`.
|
|||
internal static DebugState m_CurrentDirtyState; |
|||
|
|||
public string queryPath |
|||
{ |
|||
get { return m_QueryPath; } |
|||
internal set { m_QueryPath = value; } |
|||
} |
|||
|
|||
public abstract object GetValue(); |
|||
|
|||
public abstract void SetValue(object value, DebugUI.IValueField field); |
|||
|
|||
public virtual void OnEnable() |
|||
{ |
|||
hideFlags = HideFlags.HideAndDontSave; |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public class DebugState<T> : DebugState |
|||
{ |
|||
[SerializeField] |
|||
protected T m_Value; |
|||
|
|||
public virtual T value |
|||
{ |
|||
get { return m_Value; } |
|||
set { m_Value = value; } |
|||
} |
|||
|
|||
public override object GetValue() |
|||
{ |
|||
return value; |
|||
} |
|||
|
|||
public override void SetValue(object value, DebugUI.IValueField field) |
|||
{ |
|||
this.value = (T)field.ValidateValue(value); |
|||
} |
|||
|
|||
public override int GetHashCode() |
|||
{ |
|||
unchecked |
|||
{ |
|||
int hash = 13; |
|||
hash = hash * 23 + m_QueryPath.GetHashCode(); |
|||
hash = hash * 23 + m_Value.GetHashCode(); |
|||
return hash; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public sealed class DebugStateAttribute : Attribute |
|||
{ |
|||
public readonly Type[] types; |
|||
|
|||
public DebugStateAttribute(params Type[] types) |
|||
{ |
|||
this.types = types; |
|||
} |
|||
} |
|||
|
|||
// Builtins
|
|||
[Serializable, DebugState(typeof(DebugUI.BoolField), typeof(DebugUI.Foldout), typeof(DebugUI.HistoryBoolField))] |
|||
public sealed class DebugStateBool : DebugState<bool> {} |
|||
|
|||
[Serializable, DebugState(typeof(DebugUI.IntField), typeof(DebugUI.EnumField), typeof(DebugUI.HistoryEnumField))] |
|||
public sealed class DebugStateInt : DebugState<int> {} |
|||
|
|||
[Serializable, DebugState(typeof(DebugUI.BitField))] |
|||
public sealed class DebugStateFlags : DebugState<Enum> { } |
|||
|
|||
[Serializable, DebugState(typeof(DebugUI.UIntField))] |
|||
public sealed class DebugStateUInt : DebugState<uint> {} |
|||
|
|||
[Serializable, DebugState(typeof(DebugUI.FloatField))] |
|||
public sealed class DebugStateFloat : DebugState<float> {} |
|||
|
|||
[Serializable, DebugState(typeof(DebugUI.ColorField))] |
|||
public sealed class DebugStateColor : DebugState<Color> {} |
|||
|
|||
[Serializable, DebugState(typeof(DebugUI.Vector2Field))] |
|||
public sealed class DebugStateVector2 : DebugState<Vector2> {} |
|||
|
|||
[Serializable, DebugState(typeof(DebugUI.Vector3Field))] |
|||
public sealed class DebugStateVector3 : DebugState<Vector3> {} |
|||
|
|||
[Serializable, DebugState(typeof(DebugUI.Vector4Field))] |
|||
public sealed class DebugStateVector4 : DebugState<Vector4> {} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 7270f3dffc138834da5642e5943f5072 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEngine; |
|||
using UnityEngine.Rendering; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
[DebugUIDrawer(typeof(DebugUI.Value))] |
|||
public sealed class DebugUIDrawerValue : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.Value>(widget); |
|||
var rect = PrepareControlRect(); |
|||
EditorGUI.LabelField(rect, EditorGUIUtility.TrTextContent(w.displayName), EditorGUIUtility.TrTextContent(w.GetValue().ToString())); |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.Button))] |
|||
public sealed class DebugUIDrawerButton : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.Button>(widget); |
|||
|
|||
var rect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect()); |
|||
if (GUI.Button(rect, w.displayName, EditorStyles.miniButton)) |
|||
{ |
|||
if (w.action != null) |
|||
w.action(); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.BoolField))] |
|||
public sealed class DebugUIDrawerBoolField : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.BoolField>(widget); |
|||
var s = Cast<DebugStateBool>(state); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
var rect = PrepareControlRect(); |
|||
bool value = EditorGUI.Toggle(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue()); |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
Apply(w, s, value); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
[DebugUIDrawer(typeof(DebugUI.HistoryBoolField))] |
|||
public sealed class DebugUIDrawerHistoryBoolField : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.HistoryBoolField>(widget); |
|||
var s = Cast<DebugStateBool>(state); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
var rect = PrepareControlRect(); |
|||
var labelRect = rect; |
|||
labelRect.width = EditorGUIUtility.labelWidth; |
|||
const int oneValueWidth = 70; |
|||
var valueRects = new Rect[w.historyDepth + 1]; |
|||
for(int i = 0; i < w.historyDepth + 1; i++) |
|||
{ |
|||
valueRects[i] = rect; |
|||
valueRects[i].x += EditorGUIUtility.labelWidth + i * oneValueWidth; |
|||
valueRects[i].width = oneValueWidth; |
|||
} |
|||
EditorGUI.LabelField(labelRect, EditorGUIUtility.TrTextContent(w.displayName)); |
|||
int indent = EditorGUI.indentLevel; |
|||
EditorGUI.indentLevel = 0; //be at left of rects
|
|||
bool value = EditorGUI.Toggle(valueRects[0], w.GetValue()); |
|||
using (new EditorGUI.DisabledScope(true)) |
|||
{ |
|||
for (int i = 0; i < w.historyDepth; i++) |
|||
EditorGUI.Toggle(valueRects[i + 1], w.GetHistoryValue(i)); |
|||
} |
|||
EditorGUI.indentLevel = indent; |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
Apply(w, s, value); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.IntField))] |
|||
public sealed class DebugUIDrawerIntField : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.IntField>(widget); |
|||
var s = Cast<DebugStateInt>(state); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
var rect = PrepareControlRect(); |
|||
int value = w.min != null && w.max != null |
|||
? EditorGUI.IntSlider(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue(), w.min(), w.max()) |
|||
: EditorGUI.IntField(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue()); |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
Apply(w, s, value); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.UIntField))] |
|||
public sealed class DebugUIDrawerUIntField : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.UIntField>(widget); |
|||
var s = Cast<DebugStateUInt>(state); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
// No UIntField so we need to max to 0 ourselves or the value will wrap around
|
|||
var rect = PrepareControlRect(); |
|||
int tmp = w.min != null && w.max != null |
|||
? EditorGUI.IntSlider(rect, EditorGUIUtility.TrTextContent(w.displayName), Mathf.Max(0, (int)w.GetValue()), Mathf.Max(0, (int)w.min()), Mathf.Max(0, (int)w.max())) |
|||
: EditorGUI.IntField(rect, EditorGUIUtility.TrTextContent(w.displayName), Mathf.Max(0, (int)w.GetValue())); |
|||
|
|||
uint value = (uint)Mathf.Max(0, tmp); |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
Apply(w, s, value); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.FloatField))] |
|||
public sealed class DebugUIDrawerFloatField : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.FloatField>(widget); |
|||
var s = Cast<DebugStateFloat>(state); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
var rect = PrepareControlRect(); |
|||
float value = w.min != null && w.max != null |
|||
? EditorGUI.Slider(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue(), w.min(), w.max()) |
|||
: EditorGUI.FloatField(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue()); |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
Apply(w, s, value); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.EnumField))] |
|||
public sealed class DebugUIDrawerEnumField : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.EnumField>(widget); |
|||
var s = Cast<DebugStateInt>(state); |
|||
|
|||
if (w.indexes == null) |
|||
w.InitIndexes(); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
int index = -1; |
|||
int value = w.GetValue(); |
|||
if (w.enumNames == null || w.enumValues == null) |
|||
{ |
|||
EditorGUILayout.LabelField("Can't draw an empty enumeration."); |
|||
} |
|||
else |
|||
{ |
|||
var rect = PrepareControlRect(); |
|||
|
|||
index = w.currentIndex; |
|||
|
|||
// Fallback just in case, we may be handling sub/sectionned enums here
|
|||
if (index < 0) |
|||
index = 0; |
|||
|
|||
index = EditorGUI.IntPopup(rect, EditorGUIUtility.TrTextContent(w.displayName), index, w.enumNames, w.indexes); |
|||
value = w.enumValues[index]; |
|||
} |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
Apply(w, s, value); |
|||
if (index > -1) |
|||
w.currentIndex = index; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
[DebugUIDrawer(typeof(DebugUI.HistoryEnumField))] |
|||
public sealed class DebugUIDrawerHistoryEnumField : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.HistoryEnumField>(widget); |
|||
var s = Cast<DebugStateInt>(state); |
|||
|
|||
if (w.indexes == null) |
|||
w.InitIndexes(); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
int index = -1; |
|||
int value = w.GetValue(); |
|||
if (w.enumNames == null || w.enumValues == null) |
|||
{ |
|||
EditorGUILayout.LabelField("Can't draw an empty enumeration."); |
|||
} |
|||
else |
|||
{ |
|||
var rect = PrepareControlRect(); |
|||
index = w.currentIndex; |
|||
|
|||
// Fallback just in case, we may be handling sub/sectionned enums here
|
|||
if (index < 0) |
|||
index = 0; |
|||
|
|||
var labelRect = rect; |
|||
labelRect.width = EditorGUIUtility.labelWidth; |
|||
const int oneValueWidth = 70; |
|||
var valueRects = new Rect[w.historyDepth + 1]; |
|||
for (int i = 0; i < w.historyDepth + 1; i++) |
|||
{ |
|||
valueRects[i] = rect; |
|||
valueRects[i].x += EditorGUIUtility.labelWidth + i * oneValueWidth; |
|||
valueRects[i].width = oneValueWidth; |
|||
} |
|||
EditorGUI.LabelField(labelRect, EditorGUIUtility.TrTextContent(w.displayName)); |
|||
int indent = EditorGUI.indentLevel; |
|||
EditorGUI.indentLevel = 0; //be at left of rects
|
|||
index = EditorGUI.IntPopup(valueRects[0], index, w.enumNames, w.indexes); |
|||
value = w.enumValues[index]; |
|||
using (new EditorGUI.DisabledScope(true)) |
|||
{ |
|||
for (int i = 0; i < w.historyDepth; i++) |
|||
EditorGUI.IntPopup(valueRects[i + 1], w.GetHistoryValue(i), w.enumNames, w.indexes); |
|||
} |
|||
EditorGUI.indentLevel = indent; |
|||
} |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
Apply(w, s, value); |
|||
if (index > -1) |
|||
w.currentIndex = index; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.BitField))] |
|||
public sealed class DebugUIDrawerBitField : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.BitField>(widget); |
|||
var s = Cast<DebugStateFlags>(state); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
Enum value = w.GetValue(); |
|||
var rect = PrepareControlRect(); |
|||
value = EditorGUI.EnumFlagsField(rect, EditorGUIUtility.TrTextContent(w.displayName), value); |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
Apply(w, s, value); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.Foldout))] |
|||
public sealed class DebugUIDrawerFoldout : DebugUIDrawer |
|||
{ |
|||
public override void Begin(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.Foldout>(widget); |
|||
var s = Cast<DebugStateBool>(state); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
Rect rect = PrepareControlRect(); |
|||
bool value = EditorGUI.Foldout(rect, w.GetValue(), EditorGUIUtility.TrTextContent(w.displayName), true); |
|||
|
|||
Rect drawRect = GUILayoutUtility.GetLastRect(); |
|||
if (w.columnLabels != null && value) |
|||
{ |
|||
const int oneColumnWidth = 70; |
|||
int indent = EditorGUI.indentLevel; |
|||
EditorGUI.indentLevel = 0; //be at left of rects
|
|||
for (int i = 0; i < w.columnLabels.Length; i++) |
|||
{ |
|||
var columnRect = drawRect; |
|||
columnRect.x += EditorGUIUtility.labelWidth + i * oneColumnWidth; |
|||
columnRect.width = oneColumnWidth; |
|||
EditorGUI.LabelField(columnRect, w.columnLabels[i] ?? "", EditorStyles.miniBoldLabel); |
|||
} |
|||
EditorGUI.indentLevel = indent; |
|||
} |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
Apply(w, s, value); |
|||
|
|||
EditorGUI.indentLevel++; |
|||
} |
|||
|
|||
public override bool OnGUI(DebugUI.Widget node, DebugState state) |
|||
{ |
|||
var s = Cast<DebugStateBool>(state); |
|||
return s.value; |
|||
} |
|||
|
|||
public override void End(DebugUI.Widget node, DebugState state) |
|||
{ |
|||
EditorGUI.indentLevel--; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.ColorField))] |
|||
public sealed class DebugUIDrawerColorField : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.ColorField>(widget); |
|||
var s = Cast<DebugStateColor>(state); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
var rect = PrepareControlRect(); |
|||
var value = EditorGUI.ColorField(rect, EditorGUIUtility.TrTextContent(w.displayName), w.GetValue(), w.showPicker, w.showAlpha, w.hdr); |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
Apply(w, s, value); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.Vector2Field))] |
|||
public sealed class DebugUIDrawerVector2Field : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.Vector2Field>(widget); |
|||
var s = Cast<DebugStateVector2>(state); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
var value = EditorGUILayout.Vector2Field(w.displayName, w.GetValue()); |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
Apply(w, s, value); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.Vector3Field))] |
|||
public sealed class DebugUIDrawerVector3Field : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.Vector3Field>(widget); |
|||
var s = Cast<DebugStateVector3>(state); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
var value = EditorGUILayout.Vector3Field(w.displayName, w.GetValue()); |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
Apply(w, s, value); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.Vector4Field))] |
|||
public sealed class DebugUIDrawerVector4Field : DebugUIDrawer |
|||
{ |
|||
public override bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
var w = Cast<DebugUI.Vector4Field>(widget); |
|||
var s = Cast<DebugStateVector4>(state); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
var value = EditorGUILayout.Vector4Field(w.displayName, w.GetValue()); |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
Apply(w, s, value); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.Container))] |
|||
public sealed class DebugUIDrawerContainer : DebugUIDrawer |
|||
{ |
|||
public override void Begin(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
if (!string.IsNullOrEmpty(widget.displayName)) |
|||
EditorGUILayout.LabelField(widget.displayName, EditorStyles.boldLabel); |
|||
|
|||
EditorGUI.indentLevel++; |
|||
} |
|||
|
|||
public override void End(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
EditorGUI.indentLevel--; |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.HBox))] |
|||
public sealed class DebugUIDrawerHBox : DebugUIDrawer |
|||
{ |
|||
public override void Begin(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
EditorGUILayout.BeginHorizontal(); |
|||
} |
|||
|
|||
public override void End(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
EditorGUILayout.EndHorizontal(); |
|||
} |
|||
} |
|||
|
|||
[DebugUIDrawer(typeof(DebugUI.VBox))] |
|||
public sealed class DebugUIDrawerVBox : DebugUIDrawer |
|||
{ |
|||
public override void Begin(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
EditorGUILayout.BeginVertical(); |
|||
} |
|||
|
|||
public override void End(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
EditorGUILayout.EndVertical(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 7f9e548e5e2920b47987881c21171ef6 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEngine; |
|||
using UnityEngine.Rendering; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
public class DebugUIDrawerAttribute : Attribute |
|||
{ |
|||
public readonly Type type; |
|||
|
|||
public DebugUIDrawerAttribute(Type type) |
|||
{ |
|||
this.type = type; |
|||
} |
|||
} |
|||
|
|||
public class DebugUIDrawer |
|||
{ |
|||
protected T Cast<T>(object o) |
|||
where T : class |
|||
{ |
|||
var casted = o as T; |
|||
string typeName = o == null ? "null" : o.GetType().ToString(); |
|||
|
|||
if (casted == null) |
|||
throw new InvalidOperationException("Can't cast " + typeName + " to " + typeof(T)); |
|||
|
|||
return casted; |
|||
} |
|||
|
|||
public virtual void Begin(DebugUI.Widget widget, DebugState state) |
|||
{} |
|||
|
|||
public virtual bool OnGUI(DebugUI.Widget widget, DebugState state) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
public virtual void End(DebugUI.Widget widget, DebugState state) |
|||
{} |
|||
|
|||
protected void Apply(DebugUI.IValueField widget, DebugState state, object value) |
|||
{ |
|||
Undo.RegisterCompleteObjectUndo(state, "Debug Property Change"); |
|||
state.SetValue(value, widget); |
|||
widget.SetValue(value); |
|||
EditorUtility.SetDirty(state); |
|||
DebugState.m_CurrentDirtyState = state; |
|||
UnityEditorInternal.InternalEditorUtility.RepaintAllViews(); |
|||
} |
|||
|
|||
protected Rect PrepareControlRect(float height = -1) |
|||
{ |
|||
if (height < 0) |
|||
height = EditorGUIUtility.singleLineHeight; |
|||
var rect = GUILayoutUtility.GetRect(1f, 1f, height, height); |
|||
rect.width -= 2f; |
|||
rect.xMin += 2f; |
|||
EditorGUIUtility.labelWidth = rect.width / 2f; |
|||
return rect; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 3581d3ff7a65eee458feb865b7f29154 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Linq; |
|||
using UnityEditor; |
|||
using UnityEditor.Rendering; |
|||
using UnityEditorInternal; |
|||
using UnityEngine.Rendering; |
|||
|
|||
namespace UnityEngine.Rendering.UI |
|||
{ |
|||
[CustomEditor(typeof(DebugUIHandlerCanvas))] |
|||
public sealed class DebugUIHandlerCanvasEditor : Editor |
|||
{ |
|||
SerializedProperty m_PanelPrefab; |
|||
SerializedProperty m_Prefabs; |
|||
ReorderableList m_PrefabList; |
|||
|
|||
static string[] s_Types; // Assembly qualified names
|
|||
static string[] s_DisplayTypes; // Pretty names
|
|||
|
|||
static DebugUIHandlerCanvasEditor() |
|||
{ |
|||
s_Types = CoreUtils.GetAllTypesDerivedFrom<DebugUI.Widget>() |
|||
.Where(t => !t.IsAbstract) |
|||
.Select(t => t.AssemblyQualifiedName) |
|||
.ToArray(); |
|||
|
|||
s_DisplayTypes = new string[s_Types.Length]; |
|||
for (int i = 0; i < s_Types.Length; i++) |
|||
s_DisplayTypes[i] = Type.GetType(s_Types[i]).Name; |
|||
} |
|||
|
|||
void OnEnable() |
|||
{ |
|||
var o = new PropertyFetcher<DebugUIHandlerCanvas>(serializedObject); |
|||
m_PanelPrefab = o.Find(x => x.panelPrefab); |
|||
m_Prefabs = o.Find(x => x.prefabs); |
|||
|
|||
m_PrefabList = new ReorderableList(serializedObject, m_Prefabs, true, true, true, true) |
|||
{ |
|||
drawHeaderCallback = rect => EditorGUI.LabelField(rect, "Widget Prefabs"), |
|||
drawElementCallback = (rect, index, isActive, isFocused) => |
|||
{ |
|||
var element = m_PrefabList.serializedProperty.GetArrayElementAtIndex(index); |
|||
rect.y += 2f; |
|||
const float kTypeWidth = 100f; |
|||
|
|||
// Type selector
|
|||
var typeProp = element.FindPropertyRelative("type"); |
|||
int typeIndex = ArrayUtility.IndexOf(s_Types, typeProp.stringValue); |
|||
typeIndex = Mathf.Max(typeIndex, 0); |
|||
typeIndex = EditorGUI.Popup(new Rect(rect.x, rect.y, kTypeWidth, EditorGUIUtility.singleLineHeight), typeIndex, s_DisplayTypes); |
|||
typeProp.stringValue = s_Types[typeIndex]; |
|||
|
|||
// Prefab
|
|||
EditorGUI.PropertyField( |
|||
new Rect(rect.x + kTypeWidth + 2f, rect.y, rect.width - kTypeWidth - 2f, EditorGUIUtility.singleLineHeight), |
|||
element.FindPropertyRelative("prefab"), GUIContent.none); |
|||
}, |
|||
onSelectCallback = list => |
|||
{ |
|||
var prefab = list.serializedProperty.GetArrayElementAtIndex(list.index).FindPropertyRelative("prefab").objectReferenceValue as GameObject; |
|||
if (prefab) |
|||
EditorGUIUtility.PingObject(prefab.gameObject); |
|||
} |
|||
}; |
|||
} |
|||
|
|||
public override void OnInspectorGUI() |
|||
{ |
|||
serializedObject.Update(); |
|||
|
|||
EditorGUILayout.PropertyField(m_PanelPrefab); |
|||
EditorGUILayout.Space(); |
|||
m_PrefabList.DoLayoutList(); |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: f1d41f85c15ac6048a850b51ff36c098 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using UnityEditor.Callbacks; |
|||
using UnityEngine; |
|||
using UnityEngine.Rendering; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
#pragma warning disable 414
|
|||
|
|||
[Serializable] |
|||
sealed class WidgetStateDictionary : SerializedDictionary<string, DebugState> {} |
|||
|
|||
sealed class DebugWindowSettings : ScriptableObject |
|||
{ |
|||
// Keep these settings in a separate scriptable object so we can handle undo/redo on them
|
|||
// without the rest of the debug window interfering
|
|||
public int currentStateHash; |
|||
public int selectedPanel; |
|||
|
|||
void OnEnable() |
|||
{ |
|||
hideFlags = HideFlags.HideAndDontSave; |
|||
} |
|||
} |
|||
|
|||
public sealed class DebugWindow : EditorWindow |
|||
{ |
|||
static readonly GUIContent k_ResetButtonContent = new GUIContent("Reset"); |
|||
//static readonly GUIContent k_SaveButtonContent = new GUIContent("Save");
|
|||
//static readonly GUIContent k_LoadButtonContent = new GUIContent("Load");
|
|||
|
|||
//static bool isMultiview = false;
|
|||
|
|||
static Styles s_Styles; |
|||
static GUIStyle s_SplitterLeft; |
|||
|
|||
static float splitterPos = 150f; |
|||
const float minSideBarWidth = 100; |
|||
const float minContentWidth = 100; |
|||
bool dragging = false; |
|||
|
|||
[SerializeField] |
|||
WidgetStateDictionary m_WidgetStates; |
|||
|
|||
[SerializeField] |
|||
DebugWindowSettings m_Settings; |
|||
|
|||
[SerializeField] |
|||
int m_DebugTreeState; |
|||
|
|||
bool m_IsDirty; |
|||
|
|||
Vector2 m_PanelScroll; |
|||
Vector2 m_ContentScroll; |
|||
|
|||
static bool s_TypeMapDirty; |
|||
static Dictionary<Type, Type> s_WidgetStateMap; // DebugUI.Widget type -> DebugState type
|
|||
static Dictionary<Type, DebugUIDrawer> s_WidgetDrawerMap; // DebugUI.Widget type -> DebugUIDrawer
|
|||
|
|||
static bool s_Open; |
|||
public static bool open |
|||
{ |
|||
get => s_Open; |
|||
private set |
|||
{ |
|||
if (s_Open ^ value) |
|||
OnDebugWindowToggled?.Invoke(value); |
|||
s_Open = value; |
|||
} |
|||
} |
|||
static event Action<bool> OnDebugWindowToggled; |
|||
|
|||
[DidReloadScripts] |
|||
static void OnEditorReload() |
|||
{ |
|||
s_TypeMapDirty = true; |
|||
|
|||
//find if it where open, relink static event end propagate the info
|
|||
open = (Resources.FindObjectsOfTypeAll<DebugWindow>()?.Length ?? 0) > 0; |
|||
if (OnDebugWindowToggled == null) |
|||
OnDebugWindowToggled += DebugManager.instance.ToggleEditorUI; |
|||
DebugManager.instance.ToggleEditorUI(open); |
|||
} |
|||
|
|||
static void RebuildTypeMaps() |
|||
{ |
|||
// Map states to widget (a single state can map to several widget types if the value to
|
|||
// serialize is the same)
|
|||
var attrType = typeof(DebugStateAttribute); |
|||
var stateTypes = CoreUtils.GetAllTypesDerivedFrom<DebugState>() |
|||
.Where( |
|||
t => t.IsDefined(attrType, false) |
|||
&& !t.IsAbstract |
|||
); |
|||
|
|||
s_WidgetStateMap = new Dictionary<Type, Type>(); |
|||
|
|||
foreach (var stateType in stateTypes) |
|||
{ |
|||
var attr = (DebugStateAttribute)stateType.GetCustomAttributes(attrType, false)[0]; |
|||
|
|||
foreach (var t in attr.types) |
|||
s_WidgetStateMap.Add(t, stateType); |
|||
} |
|||
|
|||
// Drawers
|
|||
attrType = typeof(DebugUIDrawerAttribute); |
|||
var types = CoreUtils.GetAllTypesDerivedFrom<DebugUIDrawer>() |
|||
.Where( |
|||
t => t.IsDefined(attrType, false) |
|||
&& !t.IsAbstract |
|||
); |
|||
|
|||
s_WidgetDrawerMap = new Dictionary<Type, DebugUIDrawer>(); |
|||
|
|||
foreach (var t in types) |
|||
{ |
|||
var attr = (DebugUIDrawerAttribute)t.GetCustomAttributes(attrType, false)[0]; |
|||
var inst = (DebugUIDrawer)Activator.CreateInstance(t); |
|||
s_WidgetDrawerMap.Add(attr.type, inst); |
|||
} |
|||
|
|||
// Done
|
|||
s_TypeMapDirty = false; |
|||
} |
|||
|
|||
[MenuItem("Window/Render Pipeline/Render Pipeline Debug", priority = 10005)] // 112 is hardcoded number given by the UxTeam to fit correctly in the Windows menu
|
|||
static void Init() |
|||
{ |
|||
var window = GetWindow<DebugWindow>(); |
|||
window.titleContent = new GUIContent("Debug"); |
|||
if(OnDebugWindowToggled == null) |
|||
OnDebugWindowToggled += DebugManager.instance.ToggleEditorUI; |
|||
open = true; |
|||
} |
|||
|
|||
void OnEnable() |
|||
{ |
|||
DebugManager.instance.refreshEditorRequested = false; |
|||
|
|||
hideFlags = HideFlags.HideAndDontSave; |
|||
autoRepaintOnSceneChange = true; |
|||
|
|||
if (m_Settings == null) |
|||
m_Settings = CreateInstance<DebugWindowSettings>(); |
|||
|
|||
// States are ScriptableObjects (necessary for Undo/Redo) but are not saved on disk so when the editor is closed then reopened, any existing debug window will have its states set to null
|
|||
// Since we don't care about persistance in this case, we just re-init everything.
|
|||
if (m_WidgetStates == null || !AreWidgetStatesValid()) |
|||
m_WidgetStates = new WidgetStateDictionary(); |
|||
|
|||
if (s_WidgetStateMap == null || s_WidgetDrawerMap == null || s_TypeMapDirty) |
|||
RebuildTypeMaps(); |
|||
|
|||
Undo.undoRedoPerformed += OnUndoRedoPerformed; |
|||
DebugManager.instance.onSetDirty += MarkDirty; |
|||
|
|||
// First init
|
|||
m_DebugTreeState = DebugManager.instance.GetState(); |
|||
UpdateWidgetStates(); |
|||
|
|||
EditorApplication.update -= Repaint; |
|||
var panels = DebugManager.instance.panels; |
|||
var selectedPanelIndex = m_Settings.selectedPanel; |
|||
if (selectedPanelIndex >= 0 |
|||
&& selectedPanelIndex < panels.Count |
|||
&& panels[selectedPanelIndex].editorForceUpdate) |
|||
EditorApplication.update += Repaint; |
|||
} |
|||
|
|||
// Note: this won't get called if the window is opened when the editor itself is closed
|
|||
void OnDestroy() |
|||
{ |
|||
open = false; |
|||
DebugManager.instance.onSetDirty -= MarkDirty; |
|||
Undo.ClearUndo(m_Settings); |
|||
|
|||
DestroyWidgetStates(); |
|||
} |
|||
|
|||
public void DestroyWidgetStates() |
|||
{ |
|||
if (m_WidgetStates != null) |
|||
{ |
|||
// Clear all the states from memory
|
|||
foreach (var state in m_WidgetStates) |
|||
{ |
|||
var s = state.Value; |
|||
Undo.ClearUndo(s); // Don't leave dangling states in the global undo/redo stack
|
|||
DestroyImmediate(s); |
|||
} |
|||
|
|||
m_WidgetStates.Clear(); |
|||
} |
|||
} |
|||
|
|||
bool AreWidgetStatesValid() |
|||
{ |
|||
foreach (var state in m_WidgetStates) |
|||
{ |
|||
if (state.Value == null) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
return true; |
|||
} |
|||
|
|||
void MarkDirty() |
|||
{ |
|||
m_IsDirty = true; |
|||
} |
|||
|
|||
// We use item states to keep a cached value of each serializable debug items in order to
|
|||
// handle domain reloads, play mode entering/exiting and undo/redo
|
|||
// Note: no removal of orphan states
|
|||
void UpdateWidgetStates() |
|||
{ |
|||
foreach (var panel in DebugManager.instance.panels) |
|||
UpdateWidgetStates(panel); |
|||
} |
|||
|
|||
void UpdateWidgetStates(DebugUI.IContainer container) |
|||
{ |
|||
// Skip runtime only containers, we won't draw them so no need to serialize them either
|
|||
var actualWidget = container as DebugUI.Widget; |
|||
if (actualWidget != null && actualWidget.isInactiveInEditor) |
|||
return; |
|||
|
|||
// Recursively update widget states
|
|||
foreach (var widget in container.children) |
|||
{ |
|||
// Skip non-serializable widgets but still traverse them in case one of their
|
|||
// children needs serialization support
|
|||
var valueField = widget as DebugUI.IValueField; |
|||
if (valueField != null) |
|||
{ |
|||
// Skip runtime & readonly only items
|
|||
if (widget.isInactiveInEditor) |
|||
return; |
|||
|
|||
var widgetType = widget.GetType(); |
|||
string guid = widget.queryPath; |
|||
Type stateType; |
|||
s_WidgetStateMap.TryGetValue(widgetType, out stateType); |
|||
|
|||
// Create missing states & recreate the ones that are null
|
|||
if (stateType != null) |
|||
{ |
|||
if (!m_WidgetStates.ContainsKey(guid) || m_WidgetStates[guid] == null) |
|||
{ |
|||
var inst = (DebugState)CreateInstance(stateType); |
|||
inst.queryPath = guid; |
|||
inst.SetValue(valueField.GetValue(), valueField); |
|||
m_WidgetStates[guid] = inst; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Recurse if the widget is a container
|
|||
var containerField = widget as DebugUI.IContainer; |
|||
if (containerField != null) |
|||
UpdateWidgetStates(containerField); |
|||
} |
|||
} |
|||
|
|||
public void ApplyStates(bool forceApplyAll = false) |
|||
{ |
|||
if (!forceApplyAll && DebugState.m_CurrentDirtyState != null) |
|||
{ |
|||
ApplyState(DebugState.m_CurrentDirtyState.queryPath, DebugState.m_CurrentDirtyState); |
|||
DebugState.m_CurrentDirtyState = null; |
|||
return; |
|||
} |
|||
|
|||
foreach (var state in m_WidgetStates) |
|||
ApplyState(state.Key, state.Value); |
|||
|
|||
DebugState.m_CurrentDirtyState = null; |
|||
} |
|||
|
|||
void ApplyState(string queryPath, DebugState state) |
|||
{ |
|||
var widget = DebugManager.instance.GetItem(queryPath) as DebugUI.IValueField; |
|||
|
|||
if (widget == null) |
|||
return; |
|||
|
|||
widget.SetValue(state.GetValue()); |
|||
} |
|||
|
|||
void OnUndoRedoPerformed() |
|||
{ |
|||
int stateHash = ComputeStateHash(); |
|||
|
|||
// Something has been undone / redone, re-apply states to the debug tree
|
|||
if (stateHash != m_Settings.currentStateHash) |
|||
{ |
|||
ApplyStates(true); |
|||
m_Settings.currentStateHash = stateHash; |
|||
} |
|||
|
|||
Repaint(); |
|||
} |
|||
|
|||
int ComputeStateHash() |
|||
{ |
|||
unchecked |
|||
{ |
|||
int hash = 13; |
|||
|
|||
foreach (var state in m_WidgetStates) |
|||
hash = hash * 23 + state.Value.GetHashCode(); |
|||
|
|||
return hash; |
|||
} |
|||
} |
|||
|
|||
void Update() |
|||
{ |
|||
// If the render pipeline asset has been reloaded we force-refresh widget states in case
|
|||
// some debug values need to be refresh/recreated as well (e.g. frame settings on HD)
|
|||
if (DebugManager.instance.refreshEditorRequested) |
|||
{ |
|||
DestroyWidgetStates(); |
|||
DebugManager.instance.refreshEditorRequested = false; |
|||
} |
|||
|
|||
int treeState = DebugManager.instance.GetState(); |
|||
|
|||
if (m_DebugTreeState != treeState || m_IsDirty) |
|||
{ |
|||
UpdateWidgetStates(); |
|||
ApplyStates(); |
|||
m_DebugTreeState = treeState; |
|||
m_IsDirty = false; |
|||
} |
|||
} |
|||
|
|||
void OnGUI() |
|||
{ |
|||
if (s_Styles == null) |
|||
{ |
|||
s_Styles = new Styles(); |
|||
s_SplitterLeft = new GUIStyle(); |
|||
} |
|||
|
|||
var panels = DebugManager.instance.panels; |
|||
int itemCount = panels.Count(x => !x.isInactiveInEditor && x.children.Count(w => !w.isInactiveInEditor) > 0); |
|||
|
|||
if (itemCount == 0) |
|||
{ |
|||
EditorGUILayout.HelpBox("No debug item found.", MessageType.Info); |
|||
return; |
|||
} |
|||
|
|||
// Background color
|
|||
var wrect = position; |
|||
wrect.x = 0; |
|||
wrect.y = 0; |
|||
var oldColor = GUI.color; |
|||
GUI.color = s_Styles.skinBackgroundColor; |
|||
GUI.DrawTexture(wrect, EditorGUIUtility.whiteTexture); |
|||
GUI.color = oldColor; |
|||
|
|||
|
|||
GUILayout.BeginHorizontal(EditorStyles.toolbar); |
|||
//isMultiview = GUILayout.Toggle(isMultiview, "multiview", EditorStyles.toolbarButton);
|
|||
//if (isMultiview)
|
|||
// EditorGUILayout.Popup(0, new[] { new GUIContent("SceneView 1"), new GUIContent("SceneView 2") }, EditorStyles.toolbarDropDown, GUILayout.Width(100f));
|
|||
GUILayout.FlexibleSpace(); |
|||
//GUILayout.Button(k_LoadButtonContent, EditorStyles.toolbarButton);
|
|||
//GUILayout.Button(k_SaveButtonContent, EditorStyles.toolbarButton);
|
|||
if (GUILayout.Button(k_ResetButtonContent, EditorStyles.toolbarButton)) |
|||
DebugManager.instance.Reset(); |
|||
GUILayout.EndHorizontal(); |
|||
|
|||
using (new EditorGUILayout.HorizontalScope()) |
|||
{ |
|||
// Side bar
|
|||
using (var scrollScope = new EditorGUILayout.ScrollViewScope(m_PanelScroll, s_Styles.sectionScrollView, GUILayout.Width(splitterPos))) |
|||
{ |
|||
GUILayout.Space(40f); |
|||
|
|||
if (m_Settings.selectedPanel >= panels.Count) |
|||
m_Settings.selectedPanel = 0; |
|||
|
|||
// Validate container id
|
|||
while (panels[m_Settings.selectedPanel].isInactiveInEditor || panels[m_Settings.selectedPanel].children.Count(x => !x.isInactiveInEditor) == 0) |
|||
{ |
|||
m_Settings.selectedPanel++; |
|||
|
|||
if (m_Settings.selectedPanel >= panels.Count) |
|||
m_Settings.selectedPanel = 0; |
|||
} |
|||
|
|||
// Root children are containers
|
|||
for (int i = 0; i < panels.Count; i++) |
|||
{ |
|||
var panel = panels[i]; |
|||
|
|||
if (panel.isInactiveInEditor) |
|||
continue; |
|||
|
|||
if (panel.children.Count(x => !x.isInactiveInEditor) == 0) |
|||
continue; |
|||
|
|||
var elementRect = GUILayoutUtility.GetRect(EditorGUIUtility.TrTextContent(panel.displayName), s_Styles.sectionElement, GUILayout.ExpandWidth(true)); |
|||
|
|||
if (m_Settings.selectedPanel == i && Event.current.type == EventType.Repaint) |
|||
s_Styles.selected.Draw(elementRect, false, false, false, false); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
GUI.Toggle(elementRect, m_Settings.selectedPanel == i, panel.displayName, s_Styles.sectionElement); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
Undo.RegisterCompleteObjectUndo(m_Settings, "Debug Panel Selection"); |
|||
var previousPanel = m_Settings.selectedPanel >= 0 && m_Settings.selectedPanel < panels.Count |
|||
? panels[m_Settings.selectedPanel] |
|||
: null; |
|||
if (previousPanel != null && previousPanel.editorForceUpdate && !panel.editorForceUpdate) |
|||
EditorApplication.update -= Repaint; |
|||
else if ((previousPanel == null || !previousPanel.editorForceUpdate) && panel.editorForceUpdate) |
|||
EditorApplication.update += Repaint; |
|||
m_Settings.selectedPanel = i; |
|||
} |
|||
} |
|||
|
|||
m_PanelScroll = scrollScope.scrollPosition; |
|||
} |
|||
|
|||
Rect splitterRect = new Rect(splitterPos - 3, 0, 6, Screen.height); |
|||
GUI.Box(splitterRect, "", s_SplitterLeft); |
|||
|
|||
GUILayout.Space(10f); |
|||
|
|||
// Main section - traverse current container
|
|||
using (var changedScope = new EditorGUI.ChangeCheckScope()) |
|||
{ |
|||
using (new EditorGUILayout.VerticalScope()) |
|||
{ |
|||
var selectedPanel = panels[m_Settings.selectedPanel]; |
|||
|
|||
GUILayout.Label(selectedPanel.displayName, s_Styles.sectionHeader); |
|||
GUILayout.Space(10f); |
|||
|
|||
using (var scrollScope = new EditorGUILayout.ScrollViewScope(m_ContentScroll)) |
|||
{ |
|||
TraverseContainerGUI(selectedPanel); |
|||
m_ContentScroll = scrollScope.scrollPosition; |
|||
} |
|||
} |
|||
|
|||
if (changedScope.changed) |
|||
{ |
|||
m_Settings.currentStateHash = ComputeStateHash(); |
|||
DebugManager.instance.ReDrawOnScreenDebug(); |
|||
} |
|||
} |
|||
|
|||
// Splitter events
|
|||
if (Event.current != null) |
|||
{ |
|||
switch (Event.current.rawType) |
|||
{ |
|||
case EventType.MouseDown: |
|||
if (splitterRect.Contains(Event.current.mousePosition)) |
|||
{ |
|||
dragging = true; |
|||
} |
|||
break; |
|||
case EventType.MouseDrag: |
|||
if (dragging) |
|||
{ |
|||
splitterPos += Event.current.delta.x; |
|||
splitterPos = Mathf.Clamp(splitterPos, minSideBarWidth, Screen.width - minContentWidth); |
|||
Repaint(); |
|||
} |
|||
break; |
|||
case EventType.MouseUp: |
|||
if (dragging) |
|||
{ |
|||
dragging = false; |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
EditorGUIUtility.AddCursorRect(splitterRect, MouseCursor.ResizeHorizontal); |
|||
} |
|||
} |
|||
|
|||
void OnWidgetGUI(DebugUI.Widget widget) |
|||
{ |
|||
if (widget.isInactiveInEditor) |
|||
return; |
|||
|
|||
DebugState state; // State will be null for stateless widget
|
|||
m_WidgetStates.TryGetValue(widget.queryPath, out state); |
|||
|
|||
DebugUIDrawer drawer; |
|||
|
|||
if (!s_WidgetDrawerMap.TryGetValue(widget.GetType(), out drawer)) |
|||
{ |
|||
EditorGUILayout.LabelField("Drawer not found (" + widget.GetType() + ")."); |
|||
} |
|||
else |
|||
{ |
|||
drawer.Begin(widget, state); |
|||
|
|||
if (drawer.OnGUI(widget, state)) |
|||
{ |
|||
var container = widget as DebugUI.IContainer; |
|||
|
|||
if (container != null) |
|||
TraverseContainerGUI(container); |
|||
} |
|||
|
|||
drawer.End(widget, state); |
|||
} |
|||
} |
|||
|
|||
void TraverseContainerGUI(DebugUI.IContainer container) |
|||
{ |
|||
// /!\ SHAAAAAAAME ALERT /!\
|
|||
// A container can change at runtime because of the way IMGUI works and how we handle
|
|||
// onValueChanged on widget so we have to take this into account while iterating
|
|||
try |
|||
{ |
|||
foreach (var widget in container.children) |
|||
OnWidgetGUI(widget); |
|||
} |
|||
catch (InvalidOperationException) |
|||
{ |
|||
Repaint(); |
|||
} |
|||
} |
|||
|
|||
public class Styles |
|||
{ |
|||
public static float s_DefaultLabelWidth = 0.5f; |
|||
|
|||
public readonly GUIStyle sectionScrollView = "PreferencesSectionBox"; |
|||
public readonly GUIStyle sectionElement = new GUIStyle("PreferencesSection"); |
|||
public readonly GUIStyle selected = "OL SelectedRow"; |
|||
public readonly GUIStyle sectionHeader = new GUIStyle(EditorStyles.largeLabel); |
|||
public readonly Color skinBackgroundColor; |
|||
|
|||
public Styles() |
|||
{ |
|||
sectionScrollView = new GUIStyle(sectionScrollView); |
|||
sectionScrollView.overflow.bottom += 1; |
|||
|
|||
sectionElement.alignment = TextAnchor.MiddleLeft; |
|||
|
|||
sectionHeader.fontStyle = FontStyle.Bold; |
|||
sectionHeader.fontSize = 18; |
|||
sectionHeader.margin.top = 10; |
|||
sectionHeader.margin.left += 1; |
|||
sectionHeader.normal.textColor = !EditorGUIUtility.isProSkin |
|||
? new Color(0.4f, 0.4f, 0.4f, 1.0f) |
|||
: new Color(0.7f, 0.7f, 0.7f, 1.0f); |
|||
|
|||
if (EditorGUIUtility.isProSkin) |
|||
{ |
|||
sectionHeader.normal.textColor = new Color(0.7f, 0.7f, 0.7f, 1.0f); |
|||
skinBackgroundColor = Color.gray * new Color(0.3f, 0.3f, 0.3f, 0.5f); |
|||
} |
|||
else |
|||
{ |
|||
sectionHeader.normal.textColor = new Color(0.4f, 0.4f, 0.4f, 1.0f); |
|||
skinBackgroundColor = Color.gray * new Color(1f, 1f, 1f, 0.32f); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
#pragma warning restore 414
|
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 41147144ff556e246b736135eb26f185 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine.Rendering.UI; |
|||
|
|||
namespace UnityEditor.Rendering.UI |
|||
{ |
|||
[CustomEditor(typeof(UIFoldout), true)] |
|||
sealed class UIFoldoutEditor : Editor |
|||
{ |
|||
SerializedProperty m_IsOn; |
|||
SerializedProperty m_Content; |
|||
SerializedProperty m_ArrowClosed; |
|||
SerializedProperty m_ArrowOpened; |
|||
|
|||
void OnEnable() |
|||
{ |
|||
var o = new PropertyFetcher<UIFoldout>(serializedObject); |
|||
m_IsOn = o.Find("m_IsOn"); |
|||
m_Content = o.Find(x => x.content); |
|||
m_ArrowClosed = o.Find(x => x.arrowClosed); |
|||
m_ArrowOpened = o.Find(x => x.arrowOpened); |
|||
} |
|||
|
|||
public override void OnInspectorGUI() |
|||
{ |
|||
serializedObject.Update(); |
|||
|
|||
EditorGUILayout.PropertyField(m_IsOn); |
|||
EditorGUILayout.PropertyField(m_Content); |
|||
EditorGUILayout.PropertyField(m_ArrowClosed); |
|||
EditorGUILayout.PropertyField(m_ArrowOpened); |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: e89075dc59755d8479f34df9e20926e1 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
public struct EditorPrefBoolFlags<T> : IEquatable<T>, IEquatable<EditorPrefBoolFlags<T>> |
|||
where T : struct, IConvertible |
|||
{ |
|||
readonly string m_Key; |
|||
|
|||
public T value |
|||
{ get => (T)(object)EditorPrefs.GetInt(m_Key); set => EditorPrefs.SetInt(m_Key, (int)(object)value); } |
|||
|
|||
public uint rawValue |
|||
{ get => (uint)EditorPrefs.GetInt(m_Key); set => EditorPrefs.SetInt(m_Key, (int)value); } |
|||
|
|||
public EditorPrefBoolFlags(string key) => m_Key = key; |
|||
|
|||
public bool Equals(T other) => (int)(object)value == (int)(object)other; |
|||
public bool Equals(EditorPrefBoolFlags<T> other) => m_Key == other.m_Key; |
|||
|
|||
public bool HasFlag(T v) => ((uint)(int)(object)v & rawValue) == (uint)(int)(object)v; |
|||
public bool SetFlag(T f, bool v) |
|||
{ |
|||
if (v) rawValue |= (uint)(int)(object)f; |
|||
else rawValue &= ~(uint)(int)(object)f; |
|||
return v; |
|||
} |
|||
|
|||
public static explicit operator T(EditorPrefBoolFlags<T> v) => v.value; |
|||
public static EditorPrefBoolFlags<T> operator |(EditorPrefBoolFlags<T> l, T r) |
|||
{ |
|||
l.rawValue |= (uint)(int)(object)r; |
|||
return l; |
|||
} |
|||
public static EditorPrefBoolFlags<T> operator &(EditorPrefBoolFlags<T> l, T r) |
|||
{ |
|||
l.rawValue &= (uint)(int)(object)r; |
|||
return l; |
|||
} |
|||
public static EditorPrefBoolFlags<T> operator ^(EditorPrefBoolFlags<T> l, T r) |
|||
{ |
|||
l.rawValue ^= (uint)(int)(object)r; |
|||
return l; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 1f766cc928a2d60469867f03d60c9f01 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
/// <summary>Used in editor drawer part to store the state of expendable areas.</summary>
|
|||
/// <typeparam name="TState">An enum to use to describe the state.</typeparam>
|
|||
/// <typeparam name="TTarget">A type given to automatically compute the key.</typeparam>
|
|||
public struct ExpandedState<TState, TTarget> |
|||
where TState : struct, IConvertible |
|||
{ |
|||
EditorPrefBoolFlags<TState> m_State; |
|||
|
|||
/// <summary>Constructor will create the key to store in the EditorPref the state given generic type passed.</summary>
|
|||
/// <param name="defaultValue">If key did not exist, it will be created with this value for initialization.</param>
|
|||
public ExpandedState(TState defaultValue, string prefix = "CoreRP") |
|||
{ |
|||
String Key = string.Format("{0}:{1}:UI_State", prefix, typeof(TTarget).Name); |
|||
m_State = new EditorPrefBoolFlags<TState>(Key); |
|||
|
|||
//register key if not already there
|
|||
if (!EditorPrefs.HasKey(Key)) |
|||
{ |
|||
EditorPrefs.SetInt(Key, (int)(object)defaultValue); |
|||
} |
|||
} |
|||
|
|||
/// <summary>Get or set the state given the mask.</summary>
|
|||
public bool this[TState mask] |
|||
{ |
|||
get { return m_State.HasFlag(mask); } |
|||
set { m_State.SetFlag(mask, value); } |
|||
} |
|||
|
|||
/// <summary>Accessor to the expended state of this specific mask.</summary>
|
|||
public bool GetExpandedAreas(TState mask) |
|||
{ |
|||
return m_State.HasFlag(mask); |
|||
} |
|||
|
|||
/// <summary>Setter to the expended state.</summary>
|
|||
public void SetExpandedAreas(TState mask, bool value) |
|||
{ |
|||
m_State.SetFlag(mask, value); |
|||
} |
|||
|
|||
/// <summary> Utility to set all states to true </summary>
|
|||
public void ExpandAll() |
|||
{ |
|||
m_State.rawValue = ~(-1); |
|||
} |
|||
|
|||
/// <summary> Utility to set all states to false </summary>
|
|||
public void CollapseAll() |
|||
{ |
|||
m_State.rawValue = 0; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 1f4f20df9923ed04ea15d89f8456b8f0 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
[InitializeOnLoad] |
|||
public class FilterWindow : EditorWindow |
|||
{ |
|||
public interface IProvider |
|||
{ |
|||
Vector2 position { get; set; } |
|||
|
|||
void CreateComponentTree(List<Element> tree); |
|||
bool GoToChild(Element element, bool addIfComponent); |
|||
} |
|||
|
|||
public static readonly float DefaultWidth = 250f; |
|||
public static readonly float DefaultHeight = 300f; |
|||
|
|||
#region BaseElements
|
|||
|
|||
public class Element : IComparable |
|||
{ |
|||
public int level; |
|||
public GUIContent content; |
|||
|
|||
public string name |
|||
{ |
|||
get { return content.text; } |
|||
} |
|||
|
|||
public int CompareTo(object o) |
|||
{ |
|||
return name.CompareTo((o as Element).name); |
|||
} |
|||
} |
|||
|
|||
[Serializable] |
|||
public class GroupElement : Element |
|||
{ |
|||
public Vector2 scroll; |
|||
public int selectedIndex; |
|||
|
|||
public bool WantsFocus { get; protected set; } |
|||
|
|||
public virtual bool ShouldDisable |
|||
{ |
|||
get { return false; } |
|||
} |
|||
|
|||
public GroupElement(int level, string name) |
|||
{ |
|||
this.level = level; |
|||
content = new GUIContent(name); |
|||
} |
|||
|
|||
public virtual bool HandleKeyboard(Event evt, FilterWindow window, Action goToParent) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
public virtual bool OnGUI(FilterWindow sFilterWindow) |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
#endregion
|
|||
|
|||
// Styles
|
|||
|
|||
class Styles |
|||
{ |
|||
public GUIStyle header = (GUIStyle)typeof(EditorStyles).GetProperty("inspectorBig", BindingFlags.Static | BindingFlags.NonPublic).GetValue(null, null); |
|||
public GUIStyle componentButton = new GUIStyle("PR Label"); |
|||
public GUIStyle groupButton; |
|||
public GUIStyle background = "grey_border"; |
|||
public GUIStyle rightArrow = "AC RightArrow"; |
|||
public GUIStyle leftArrow = "AC LeftArrow"; |
|||
|
|||
public Styles() |
|||
{ |
|||
header.font = EditorStyles.boldLabel.font; |
|||
|
|||
componentButton.alignment = TextAnchor.MiddleLeft; |
|||
componentButton.fixedHeight = 20; |
|||
componentButton.imagePosition = ImagePosition.ImageAbove; |
|||
|
|||
groupButton = new GUIStyle(componentButton); |
|||
} |
|||
} |
|||
|
|||
const int k_HeaderHeight = 30; |
|||
const int k_WindowHeight = 400 - 80; |
|||
const int k_HelpHeight = 80 * 0; |
|||
const string k_ComponentSearch = "NodeSearchString"; |
|||
|
|||
static Styles s_Styles; |
|||
static FilterWindow s_FilterWindow; |
|||
static long s_LastClosedTime; |
|||
static bool s_DirtyList; |
|||
|
|||
IProvider m_Provider; |
|||
Element[] m_Tree; |
|||
Element[] m_SearchResultTree; |
|||
List<GroupElement> m_Stack = new List<GroupElement>(); |
|||
|
|||
float m_Anim = 1; |
|||
int m_AnimTarget = 1; |
|||
long m_LastTime; |
|||
bool m_ScrollToSelected; |
|||
string m_DelayedSearch; |
|||
string m_Search = ""; |
|||
|
|||
bool m_HasSearch { get { return !string.IsNullOrEmpty(m_Search); } } |
|||
GroupElement m_ActiveParent { get { return m_Stack[m_Stack.Count - 2 + m_AnimTarget]; } } |
|||
Element[] m_ActiveTree { get { return m_HasSearch ? m_SearchResultTree : m_Tree; } } |
|||
Element m_ActiveElement |
|||
{ |
|||
get |
|||
{ |
|||
if (m_ActiveTree == null) |
|||
return null; |
|||
|
|||
var children = GetChildren(m_ActiveTree, m_ActiveParent); |
|||
return children.Count == 0 |
|||
? null |
|||
: children[m_ActiveParent.selectedIndex]; |
|||
} |
|||
} |
|||
bool m_IsAnimating { get { return !Mathf.Approximately(m_Anim, m_AnimTarget); } } |
|||
|
|||
static FilterWindow() |
|||
{ |
|||
s_DirtyList = true; |
|||
} |
|||
|
|||
void OnEnable() |
|||
{ |
|||
s_FilterWindow = this; |
|||
m_Search = ""; |
|||
} |
|||
|
|||
void OnDisable() |
|||
{ |
|||
s_LastClosedTime = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; |
|||
s_FilterWindow = null; |
|||
} |
|||
|
|||
internal static bool ValidateAddComponentMenuItem() |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
internal static bool Show(Vector2 position, IProvider provider) |
|||
{ |
|||
// If the window is already open, close it instead.
|
|||
var wins = Resources.FindObjectsOfTypeAll(typeof(FilterWindow)); |
|||
if (wins.Length > 0) |
|||
{ |
|||
try |
|||
{ |
|||
((EditorWindow)wins[0]).Close(); |
|||
return false; |
|||
} |
|||
catch (Exception) |
|||
{ |
|||
s_FilterWindow = null; |
|||
} |
|||
} |
|||
|
|||
// We could not use realtimeSinceStartUp since it is set to 0 when entering/exitting
|
|||
// playmode, we assume an increasing time when comparing time.
|
|||
long nowMilliSeconds = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond; |
|||
bool justClosed = nowMilliSeconds < s_LastClosedTime + 50; |
|||
|
|||
if (!justClosed) |
|||
{ |
|||
Event.current.Use(); |
|||
|
|||
if (s_FilterWindow == null) |
|||
{ |
|||
s_FilterWindow = CreateInstance<FilterWindow>(); |
|||
s_FilterWindow.hideFlags = HideFlags.HideAndDontSave; |
|||
} |
|||
|
|||
s_FilterWindow.Init(position, provider); |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
static object Invoke(Type t, object inst, string method, params object[] args) |
|||
{ |
|||
var flags = (inst == null ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.NonPublic; |
|||
var mi = t.GetMethod(method, flags); |
|||
return mi.Invoke(inst, args); |
|||
} |
|||
|
|||
void Init(Vector2 position, IProvider provider) |
|||
{ |
|||
m_Provider = provider; |
|||
// Has to be done before calling Show / ShowWithMode
|
|||
m_Provider.position = position; |
|||
position = GUIUtility.GUIToScreenPoint(position); |
|||
var buttonRect = new Rect(position.x - DefaultWidth / 2, position.y - 16, DefaultWidth, 1); |
|||
|
|||
CreateComponentTree(); |
|||
|
|||
ShowAsDropDown(buttonRect, new Vector2(buttonRect.width, k_WindowHeight)); |
|||
|
|||
Focus(); |
|||
|
|||
wantsMouseMove = true; |
|||
} |
|||
|
|||
void CreateComponentTree() |
|||
{ |
|||
var tree = new List<Element>(); |
|||
m_Provider.CreateComponentTree(tree); |
|||
|
|||
m_Tree = tree.ToArray(); |
|||
|
|||
// Rebuild stack
|
|||
if (m_Stack.Count == 0) |
|||
{ |
|||
m_Stack.Add(m_Tree[0] as GroupElement); |
|||
} |
|||
else |
|||
{ |
|||
// The root is always the match for level 0
|
|||
var match = m_Tree[0] as GroupElement; |
|||
int level = 0; |
|||
while (true) |
|||
{ |
|||
// Assign the match for the current level
|
|||
var oldElement = m_Stack[level]; |
|||
m_Stack[level] = match; |
|||
m_Stack[level].selectedIndex = oldElement.selectedIndex; |
|||
m_Stack[level].scroll = oldElement.scroll; |
|||
|
|||
// See if we reached last element of stack
|
|||
level++; |
|||
if (level == m_Stack.Count) |
|||
break; |
|||
|
|||
// Try to find a child of the same name as we had before
|
|||
var children = GetChildren(m_ActiveTree, match); |
|||
var childMatch = children.FirstOrDefault(c => c.name == m_Stack[level].name); |
|||
|
|||
if (childMatch is GroupElement) |
|||
{ |
|||
match = childMatch as GroupElement; |
|||
} |
|||
else |
|||
{ |
|||
// If we couldn't find the child, remove all further elements from the stack
|
|||
while (m_Stack.Count > level) |
|||
m_Stack.RemoveAt(level); |
|||
} |
|||
} |
|||
} |
|||
|
|||
s_DirtyList = false; |
|||
RebuildSearch(); |
|||
} |
|||
|
|||
internal void OnGUI() |
|||
{ |
|||
// Avoids errors in the console if a domain reload is triggered while the filter window
|
|||
// is opened
|
|||
if (m_Provider == null) |
|||
return; |
|||
|
|||
if (s_Styles == null) |
|||
s_Styles = new Styles(); |
|||
|
|||
GUI.Label(new Rect(0, 0, position.width, position.height), GUIContent.none, s_Styles.background); |
|||
|
|||
if (s_DirtyList) |
|||
CreateComponentTree(); |
|||
|
|||
// Keyboard
|
|||
HandleKeyboard(); |
|||
|
|||
GUILayout.Space(7); |
|||
|
|||
// Search
|
|||
if (!m_ActiveParent.WantsFocus) |
|||
{ |
|||
EditorGUI.FocusTextInControl("ComponentSearch"); |
|||
Focus(); |
|||
} |
|||
|
|||
var searchRect = GUILayoutUtility.GetRect(10, 20); |
|||
searchRect.x += 8; |
|||
searchRect.width -= 16; |
|||
|
|||
GUI.SetNextControlName("ComponentSearch"); |
|||
|
|||
using (new EditorGUI.DisabledScope(m_ActiveParent.ShouldDisable)) |
|||
{ |
|||
string newSearch = (string)Invoke(typeof(EditorGUI), null, "SearchField", searchRect, m_DelayedSearch ?? m_Search); |
|||
|
|||
if (newSearch != m_Search || m_DelayedSearch != null) |
|||
{ |
|||
if (!m_IsAnimating) |
|||
{ |
|||
m_Search = m_DelayedSearch ?? newSearch; |
|||
EditorPrefs.SetString(k_ComponentSearch, m_Search); |
|||
RebuildSearch(); |
|||
m_DelayedSearch = null; |
|||
} |
|||
else |
|||
{ |
|||
m_DelayedSearch = newSearch; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Show lists
|
|||
ListGUI(m_ActiveTree, m_Anim, GetElementRelative(0), GetElementRelative(-1)); |
|||
if (m_Anim < 1) |
|||
ListGUI(m_ActiveTree, m_Anim + 1, GetElementRelative(-1), GetElementRelative(-2)); |
|||
|
|||
// Animate
|
|||
if (m_IsAnimating && Event.current.type == EventType.Repaint) |
|||
{ |
|||
long now = DateTime.Now.Ticks; |
|||
float deltaTime = (now - m_LastTime) / (float)TimeSpan.TicksPerSecond; |
|||
m_LastTime = now; |
|||
m_Anim = Mathf.MoveTowards(m_Anim, m_AnimTarget, deltaTime * 4); |
|||
|
|||
if (m_AnimTarget == 0 && Mathf.Approximately(m_Anim, 0)) |
|||
{ |
|||
m_Anim = 1; |
|||
m_AnimTarget = 1; |
|||
m_Stack.RemoveAt(m_Stack.Count - 1); |
|||
} |
|||
|
|||
Repaint(); |
|||
} |
|||
} |
|||
|
|||
void HandleKeyboard() |
|||
{ |
|||
var evt = Event.current; |
|||
|
|||
if (evt.type == EventType.KeyDown) |
|||
{ |
|||
// Special handling when in new script panel
|
|||
if (!m_ActiveParent.HandleKeyboard(evt, s_FilterWindow, GoToParent)) |
|||
{ |
|||
// Always do these
|
|||
if (evt.keyCode == KeyCode.DownArrow) |
|||
{ |
|||
m_ActiveParent.selectedIndex++; |
|||
m_ActiveParent.selectedIndex = Mathf.Min(m_ActiveParent.selectedIndex, GetChildren(m_ActiveTree, m_ActiveParent).Count - 1); |
|||
m_ScrollToSelected = true; |
|||
evt.Use(); |
|||
} |
|||
|
|||
if (evt.keyCode == KeyCode.UpArrow) |
|||
{ |
|||
m_ActiveParent.selectedIndex--; |
|||
m_ActiveParent.selectedIndex = Mathf.Max(m_ActiveParent.selectedIndex, 0); |
|||
m_ScrollToSelected = true; |
|||
evt.Use(); |
|||
} |
|||
|
|||
if (evt.keyCode == KeyCode.Return || evt.keyCode == KeyCode.KeypadEnter) |
|||
{ |
|||
GoToChild(m_ActiveElement, true); |
|||
evt.Use(); |
|||
} |
|||
|
|||
// Do these if we're not in search mode
|
|||
if (!m_HasSearch) |
|||
{ |
|||
if (evt.keyCode == KeyCode.LeftArrow || evt.keyCode == KeyCode.Backspace) |
|||
{ |
|||
GoToParent(); |
|||
evt.Use(); |
|||
} |
|||
|
|||
if (evt.keyCode == KeyCode.RightArrow) |
|||
{ |
|||
GoToChild(m_ActiveElement, false); |
|||
evt.Use(); |
|||
} |
|||
|
|||
if (evt.keyCode == KeyCode.Escape) |
|||
{ |
|||
Close(); |
|||
evt.Use(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
const string k_SearchHeader = "Search"; |
|||
|
|||
void RebuildSearch() |
|||
{ |
|||
if (!m_HasSearch) |
|||
{ |
|||
m_SearchResultTree = null; |
|||
|
|||
if (m_Stack[m_Stack.Count - 1].name == k_SearchHeader) |
|||
{ |
|||
m_Stack.Clear(); |
|||
m_Stack.Add(m_Tree[0] as GroupElement); |
|||
} |
|||
|
|||
m_AnimTarget = 1; |
|||
m_LastTime = DateTime.Now.Ticks; |
|||
return; |
|||
} |
|||
|
|||
// Support multiple search words separated by spaces.
|
|||
var searchWords = m_Search.ToLower().Split(' '); |
|||
|
|||
// We keep two lists. Matches that matches the start of an item always get first priority.
|
|||
var matchesStart = new List<Element>(); |
|||
var matchesWithin = new List<Element>(); |
|||
|
|||
foreach (var e in m_Tree) |
|||
{ |
|||
if (e is GroupElement) |
|||
continue; |
|||
|
|||
string name = e.name.ToLower().Replace(" ", ""); |
|||
bool didMatchAll = true; |
|||
bool didMatchStart = false; |
|||
|
|||
// See if we match ALL the seaarch words.
|
|||
for (int w = 0; w < searchWords.Length; w++) |
|||
{ |
|||
string search = searchWords[w]; |
|||
|
|||
if (name.Contains(search)) |
|||
{ |
|||
// If the start of the item matches the first search word, make a note of that.
|
|||
if (w == 0 && name.StartsWith(search)) |
|||
didMatchStart = true; |
|||
} |
|||
else |
|||
{ |
|||
// As soon as any word is not matched, we disregard this item.
|
|||
didMatchAll = false; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
// We always need to match all search words.
|
|||
// If we ALSO matched the start, this item gets priority.
|
|||
if (didMatchAll) |
|||
{ |
|||
if (didMatchStart) |
|||
matchesStart.Add(e); |
|||
else |
|||
matchesWithin.Add(e); |
|||
} |
|||
} |
|||
|
|||
matchesStart.Sort(); |
|||
matchesWithin.Sort(); |
|||
|
|||
// Create search tree
|
|||
var tree = new List<Element>(); |
|||
|
|||
// Add parent
|
|||
tree.Add(new GroupElement(0, k_SearchHeader)); |
|||
|
|||
// Add search results
|
|||
tree.AddRange(matchesStart); |
|||
tree.AddRange(matchesWithin); |
|||
|
|||
// Create search result tree
|
|||
m_SearchResultTree = tree.ToArray(); |
|||
m_Stack.Clear(); |
|||
m_Stack.Add(m_SearchResultTree[0] as GroupElement); |
|||
|
|||
// Always select the first search result when search is changed (e.g. a character was typed in or deleted),
|
|||
// because it's usually the best match.
|
|||
if (GetChildren(m_ActiveTree, m_ActiveParent).Count >= 1) |
|||
m_ActiveParent.selectedIndex = 0; |
|||
else |
|||
m_ActiveParent.selectedIndex = -1; |
|||
} |
|||
|
|||
GroupElement GetElementRelative(int rel) |
|||
{ |
|||
int i = m_Stack.Count + rel - 1; |
|||
|
|||
if (i < 0) |
|||
return null; |
|||
|
|||
return m_Stack[i]; |
|||
} |
|||
|
|||
void GoToParent() |
|||
{ |
|||
if (m_Stack.Count > 1) |
|||
{ |
|||
m_AnimTarget = 0; |
|||
m_LastTime = DateTime.Now.Ticks; |
|||
} |
|||
} |
|||
|
|||
void ListGUI(Element[] tree, float anim, GroupElement parent, GroupElement grandParent) |
|||
{ |
|||
// Smooth the fractional part of the anim value
|
|||
anim = Mathf.Floor(anim) + Mathf.SmoothStep(0, 1, Mathf.Repeat(anim, 1)); |
|||
|
|||
// Calculate rect for animated area
|
|||
var animRect = position; |
|||
animRect.x = position.width * (1 - anim) + 1; |
|||
animRect.y = k_HeaderHeight; |
|||
animRect.height -= k_HeaderHeight + k_HelpHeight; |
|||
animRect.width -= 2; |
|||
|
|||
// Start of animated area (the part that moves left and right)
|
|||
GUILayout.BeginArea(animRect); |
|||
|
|||
// Header
|
|||
var headerRect = GUILayoutUtility.GetRect(10, 25); |
|||
string name = parent.name; |
|||
GUI.Label(headerRect, name, s_Styles.header); |
|||
|
|||
// Back button
|
|||
if (grandParent != null) |
|||
{ |
|||
var arrowRect = new Rect(headerRect.x + 4, headerRect.y + 7, 13, 13); |
|||
var e = Event.current; |
|||
|
|||
if (e.type == EventType.Repaint) |
|||
s_Styles.leftArrow.Draw(arrowRect, false, false, false, false); |
|||
|
|||
if (e.type == EventType.MouseDown && headerRect.Contains(e.mousePosition)) |
|||
{ |
|||
GoToParent(); |
|||
e.Use(); |
|||
} |
|||
} |
|||
|
|||
if (!parent.OnGUI(s_FilterWindow)) |
|||
ListGUI(tree, parent); |
|||
|
|||
GUILayout.EndArea(); |
|||
} |
|||
|
|||
void GoToChild(Element e, bool addIfComponent) |
|||
{ |
|||
if (m_Provider.GoToChild(e, addIfComponent)) |
|||
{ |
|||
Close(); |
|||
} |
|||
else if (!m_HasSearch) |
|||
{ |
|||
m_LastTime = DateTime.Now.Ticks; |
|||
|
|||
if (m_AnimTarget == 0) |
|||
{ |
|||
m_AnimTarget = 1; |
|||
} |
|||
else if (Mathf.Approximately(m_Anim, 1f)) |
|||
{ |
|||
m_Anim = 0; |
|||
m_Stack.Add(e as GroupElement); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void ListGUI(Element[] tree, GroupElement parent) |
|||
{ |
|||
// Start of scroll view list
|
|||
parent.scroll = GUILayout.BeginScrollView(parent.scroll); |
|||
|
|||
EditorGUIUtility.SetIconSize(new Vector2(16, 16)); |
|||
|
|||
var children = GetChildren(tree, parent); |
|||
var selectedRect = new Rect(); |
|||
var evt = Event.current; |
|||
|
|||
// Iterate through the children
|
|||
for (int i = 0; i < children.Count; i++) |
|||
{ |
|||
var e = children[i]; |
|||
var r = GUILayoutUtility.GetRect(16, 20, GUILayout.ExpandWidth(true)); |
|||
|
|||
// Select the element the mouse cursor is over.
|
|||
// Only do it on mouse move - keyboard controls are allowed to overwrite this until the next time the mouse moves.
|
|||
if (evt.type == EventType.MouseMove || evt.type == EventType.MouseDown) |
|||
{ |
|||
if (parent.selectedIndex != i && r.Contains(evt.mousePosition)) |
|||
{ |
|||
parent.selectedIndex = i; |
|||
Repaint(); |
|||
} |
|||
} |
|||
|
|||
bool selected = false; |
|||
|
|||
// Handle selected item
|
|||
if (i == parent.selectedIndex) |
|||
{ |
|||
selected = true; |
|||
selectedRect = r; |
|||
} |
|||
|
|||
// Draw element
|
|||
if (evt.type == EventType.Repaint) |
|||
{ |
|||
var labelStyle = (e is GroupElement) ? s_Styles.groupButton : s_Styles.componentButton; |
|||
labelStyle.Draw(r, e.content, false, false, selected, selected); |
|||
|
|||
if (e is GroupElement) |
|||
{ |
|||
var arrowRect = new Rect(r.x + r.width - 13, r.y + 4, 13, 13); |
|||
s_Styles.rightArrow.Draw(arrowRect, false, false, false, false); |
|||
} |
|||
} |
|||
|
|||
if (evt.type == EventType.MouseDown && r.Contains(evt.mousePosition)) |
|||
{ |
|||
evt.Use(); |
|||
parent.selectedIndex = i; |
|||
GoToChild(e, true); |
|||
} |
|||
} |
|||
|
|||
EditorGUIUtility.SetIconSize(Vector2.zero); |
|||
|
|||
GUILayout.EndScrollView(); |
|||
|
|||
// Scroll to show selected
|
|||
if (m_ScrollToSelected && evt.type == EventType.Repaint) |
|||
{ |
|||
m_ScrollToSelected = false; |
|||
var scrollRect = GUILayoutUtility.GetLastRect(); |
|||
|
|||
if (selectedRect.yMax - scrollRect.height > parent.scroll.y) |
|||
{ |
|||
parent.scroll.y = selectedRect.yMax - scrollRect.height; |
|||
Repaint(); |
|||
} |
|||
|
|||
if (selectedRect.y < parent.scroll.y) |
|||
{ |
|||
parent.scroll.y = selectedRect.y; |
|||
Repaint(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
List<Element> GetChildren(Element[] tree, Element parent) |
|||
{ |
|||
var children = new List<Element>(); |
|||
int level = -1; |
|||
int i; |
|||
|
|||
for (i = 0; i < tree.Length; i++) |
|||
{ |
|||
if (tree[i] == parent) |
|||
{ |
|||
level = parent.level + 1; |
|||
i++; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
if (level == -1) |
|||
return children; |
|||
|
|||
for (; i < tree.Length; i++) |
|||
{ |
|||
var e = tree[i]; |
|||
|
|||
if (e.level < level) |
|||
break; |
|||
|
|||
if (e.level > level && !m_HasSearch) |
|||
continue; |
|||
|
|||
children.Add(e); |
|||
} |
|||
|
|||
return children; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: e1dbde6eddba87f4ea272a5d3af5b7ac |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: bce47e7c42ffde449812bbbf027555d7 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEngine; |
|||
using System.Reflection; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Provide a gizmo/handle representing a box where all face can be moved independently.
|
|||
/// Also add a contained sub gizmo/handle box if contained is used at creation.
|
|||
/// </summary>
|
|||
/// <example>
|
|||
/// <code>
|
|||
/// class MyComponentEditor : Editor
|
|||
/// {
|
|||
/// static HierarchicalBox box;
|
|||
/// static HierarchicalBox containedBox;
|
|||
///
|
|||
/// static MyComponentEditor()
|
|||
/// {
|
|||
/// Color[] handleColors = new Color[]
|
|||
/// {
|
|||
/// Color.red,
|
|||
/// Color.green,
|
|||
/// Color.Blue,
|
|||
/// new Color(0.5f, 0f, 0f, 1f),
|
|||
/// new Color(0f, 0.5f, 0f, 1f),
|
|||
/// new Color(0f, 0f, 0.5f, 1f)
|
|||
/// };
|
|||
/// box = new HierarchicalBox(new Color(1f, 1f, 1f, 0.25), handleColors);
|
|||
/// containedBox = new HierarchicalBox(new Color(1f, 0f, 1f, 0.25), handleColors, container: box);
|
|||
/// }
|
|||
///
|
|||
/// [DrawGizmo(GizmoType.Selected|GizmoType.Active)]
|
|||
/// void DrawGizmo(MyComponent comp, GizmoType gizmoType)
|
|||
/// {
|
|||
/// box.center = comp.transform.position;
|
|||
/// box.size = comp.transform.scale;
|
|||
/// box.DrawHull(gizmoType == GizmoType.Selected);
|
|||
///
|
|||
/// containedBox.center = comp.innerposition;
|
|||
/// containedBox.size = comp.innerScale;
|
|||
/// containedBox.DrawHull(gizmoType == GizmoType.Selected);
|
|||
/// }
|
|||
///
|
|||
/// void OnSceneGUI()
|
|||
/// {
|
|||
/// EditorGUI.BeginChangeCheck();
|
|||
///
|
|||
/// //container box must be also set for contained box for clamping
|
|||
/// box.center = comp.transform.position;
|
|||
/// box.size = comp.transform.scale;
|
|||
/// box.DrawHandle();
|
|||
///
|
|||
/// containedBox.DrawHandle();
|
|||
/// containedBox.center = comp.innerposition;
|
|||
/// containedBox.size = comp.innerScale;
|
|||
///
|
|||
/// if(EditorGUI.EndChangeCheck())
|
|||
/// {
|
|||
/// comp.innerposition = containedBox.center;
|
|||
/// comp.innersize = containedBox.size;
|
|||
/// }
|
|||
/// }
|
|||
/// }
|
|||
/// </code>
|
|||
/// </example>
|
|||
public class HierarchicalBox |
|||
{ |
|||
const float k_HandleSizeCoef = 0.05f; |
|||
static Material k_Material_Cache; |
|||
static Material k_Material => (k_Material_Cache == null || k_Material_Cache.Equals(null) ? (k_Material_Cache = new Material(Shader.Find("Hidden/UnlitTransparentColored"))) : k_Material_Cache); |
|||
static Mesh k_MeshQuad_Cache; |
|||
static Mesh k_MeshQuad => k_MeshQuad_Cache == null || k_MeshQuad_Cache.Equals(null) ? (k_MeshQuad_Cache = Resources.GetBuiltinResource<Mesh>("Quad.fbx")) : k_MeshQuad_Cache; |
|||
|
|||
enum NamedFace { Right, Top, Front, Left, Bottom, Back, None } |
|||
|
|||
Material m_Material; |
|||
readonly Color[] m_PolychromeHandleColor; |
|||
readonly HierarchicalBox m_Parent; |
|||
Color m_MonochromeHandleColor; |
|||
Color m_WireframeColor; |
|||
Color m_WireframeColorBehind; |
|||
int[] m_ControlIDs = new int[6] { 0, 0, 0, 0, 0, 0 }; |
|||
bool m_MonoHandle = true; |
|||
|
|||
Material material => m_Material == null || m_Material.Equals(null) |
|||
? (m_Material = new Material(k_Material)) |
|||
: m_Material; |
|||
|
|||
/// <summary>
|
|||
/// Allow to switch between the mode where all axis are controlled together or not
|
|||
/// Note that if there is several handles, they will use the polychrome colors.
|
|||
/// </summary>
|
|||
public bool monoHandle { get => m_MonoHandle; set => m_MonoHandle = value; } |
|||
|
|||
/// <summary>The position of the center of the box in Handle.matrix space.</summary>
|
|||
public Vector3 center { get; set; } |
|||
|
|||
/// <summary>The size of the box in Handle.matrix space.</summary>
|
|||
public Vector3 size { get; set; } |
|||
|
|||
/// <summary>The baseColor used to fill hull. All other colors are deduced from it except specific handle colors.</summary>
|
|||
public Color baseColor |
|||
{ |
|||
get { return material.color; } |
|||
set |
|||
{ |
|||
value.a = 8f / 255; |
|||
material.color = value; |
|||
value.a = 1f; |
|||
m_MonochromeHandleColor = value; |
|||
value.a = 0.7f; |
|||
m_WireframeColor = value; |
|||
value.a = 0.2f; |
|||
m_WireframeColorBehind = value; |
|||
} |
|||
} |
|||
|
|||
//Note: Handles.Slider not allow to use a specific ControlID.
|
|||
//Thus Slider1D is used (with reflection)
|
|||
static Type k_Slider1D = Type.GetType("UnityEditorInternal.Slider1D, UnityEditor"); |
|||
static MethodInfo k_Slider1D_Do = k_Slider1D |
|||
.GetMethod( |
|||
"Do", |
|||
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public, |
|||
null, |
|||
CallingConventions.Any, |
|||
new[] { typeof(int), typeof(Vector3), typeof(Vector3), typeof(float), typeof(Handles.CapFunction), typeof(float) }, |
|||
null); |
|||
static void Slider1D(int controlID, ref Vector3 handlePosition, Vector3 handleOrientation, float snapScale, Color color) |
|||
{ |
|||
using (new Handles.DrawingScope(color)) |
|||
{ |
|||
handlePosition = (Vector3)k_Slider1D_Do.Invoke(null, new object[] |
|||
{ |
|||
controlID, |
|||
handlePosition, |
|||
handleOrientation, |
|||
HandleUtility.GetHandleSize(handlePosition) * k_HandleSizeCoef, |
|||
new Handles.CapFunction(Handles.DotHandleCap), |
|||
snapScale |
|||
}); |
|||
} |
|||
} |
|||
|
|||
/// <summary>Constructor. Used to setup colors and also the container if any.</summary>
|
|||
/// <param name="baseColor">The color of each face of the box. Other colors are deduced from it.</param>
|
|||
/// <param name="polychromeHandleColors">The color of handle when they are separated. When they are grouped, they use a variation of the faceColor instead.</param>
|
|||
/// <param name="parent">The HierarchicalBox containing this box. If null, the box will not be limited in size.</param>
|
|||
public HierarchicalBox(Color baseColor, Color[] polychromeHandleColors = null, HierarchicalBox parent = null) |
|||
{ |
|||
if (polychromeHandleColors != null && polychromeHandleColors.Length != 6) |
|||
throw new ArgumentException("polychromeHandleColors must be null or have a size of 6."); |
|||
|
|||
m_Parent = parent; |
|||
m_Material = new Material(k_Material); |
|||
this.baseColor = baseColor; |
|||
m_PolychromeHandleColor = polychromeHandleColors ?? new Color[] |
|||
{ |
|||
Handles.xAxisColor, Handles.yAxisColor, Handles.zAxisColor, |
|||
Handles.xAxisColor, Handles.yAxisColor, Handles.zAxisColor |
|||
}; |
|||
} |
|||
|
|||
/// <summary>Draw the hull which means the boxes without the handles</summary>
|
|||
public void DrawHull(bool filled) |
|||
{ |
|||
Color previousColor = Handles.color; |
|||
if (filled) |
|||
{ |
|||
// Draw the hull
|
|||
var xSize = new Vector3(size.z, size.y, 1f); |
|||
material.SetPass(0); |
|||
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.x * .5f * Vector3.left, Quaternion.FromToRotation(Vector3.forward, Vector3.left), xSize)); |
|||
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.x * .5f * Vector3.right, Quaternion.FromToRotation(Vector3.forward, Vector3.right), xSize)); |
|||
|
|||
var ySize = new Vector3(size.x, size.z, 1f); |
|||
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.y * .5f * Vector3.up, Quaternion.FromToRotation(Vector3.forward, Vector3.up), ySize)); |
|||
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.y * .5f * Vector3.down, Quaternion.FromToRotation(Vector3.forward, Vector3.down), ySize)); |
|||
|
|||
var zSize = new Vector3(size.x, size.y, 1f); |
|||
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.z * .5f * Vector3.forward, Quaternion.identity, zSize)); |
|||
Graphics.DrawMeshNow(k_MeshQuad, Handles.matrix * Matrix4x4.TRS(center + size.z * .5f * Vector3.back, Quaternion.FromToRotation(Vector3.forward, Vector3.back), zSize)); |
|||
|
|||
//if as a parent, also draw handle distance to the parent
|
|||
if (m_Parent != null) |
|||
{ |
|||
var centerDiff = center - m_Parent.center; |
|||
var xRecal = centerDiff; |
|||
var yRecal = centerDiff; |
|||
var zRecal = centerDiff; |
|||
xRecal.x = 0; |
|||
yRecal.y = 0; |
|||
zRecal.z = 0; |
|||
|
|||
Handles.color = GetHandleColor(NamedFace.Left); |
|||
Handles.DrawLine(m_Parent.center + xRecal + m_Parent.size.x * .5f * Vector3.left, center + size.x * .5f * Vector3.left); |
|||
|
|||
Handles.color = GetHandleColor(NamedFace.Right); |
|||
Handles.DrawLine(m_Parent.center + xRecal + m_Parent.size.x * .5f * Vector3.right, center + size.x * .5f * Vector3.right); |
|||
|
|||
Handles.color = GetHandleColor(NamedFace.Top); |
|||
Handles.DrawLine(m_Parent.center + yRecal + m_Parent.size.y * .5f * Vector3.up, center + size.y * .5f * Vector3.up); |
|||
|
|||
Handles.color = GetHandleColor(NamedFace.Bottom); |
|||
Handles.DrawLine(m_Parent.center + yRecal + m_Parent.size.y * .5f * Vector3.down, center + size.y * .5f * Vector3.down); |
|||
|
|||
Handles.color = GetHandleColor(NamedFace.Front); |
|||
Handles.DrawLine(m_Parent.center + zRecal + m_Parent.size.z * .5f * Vector3.forward, center + size.z * .5f * Vector3.forward); |
|||
|
|||
Handles.color = GetHandleColor(NamedFace.Back); |
|||
Handles.DrawLine(m_Parent.center + zRecal + m_Parent.size.z * .5f * Vector3.back, center + size.z * .5f * Vector3.back); |
|||
} |
|||
} |
|||
|
|||
Handles.color = m_WireframeColor; |
|||
Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual; |
|||
Handles.DrawWireCube(center, size); |
|||
Handles.color = m_WireframeColorBehind; |
|||
Handles.zTest = UnityEngine.Rendering.CompareFunction.Greater; |
|||
Handles.DrawWireCube(center, size); |
|||
Handles.zTest = UnityEngine.Rendering.CompareFunction.Always; |
|||
Handles.color = previousColor; |
|||
} |
|||
|
|||
/// <summary>Draw the manipulable handles</summary>
|
|||
public void DrawHandle() |
|||
{ |
|||
Event evt = Event.current; |
|||
bool useHomothety = evt.shift; |
|||
bool useSymetry = evt.alt || evt.command; |
|||
// Note: snapping is handled natively on ctrl for each Slider1D
|
|||
|
|||
for (int i = 0, count = m_ControlIDs.Length; i < count; ++i) |
|||
m_ControlIDs[i] = GUIUtility.GetControlID("HierarchicalBox".GetHashCode() + i, FocusType.Passive); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
|
|||
var leftPosition = center + size.x * .5f * Vector3.left; |
|||
var rightPosition = center + size.x * .5f * Vector3.right; |
|||
var topPosition = center + size.y * .5f * Vector3.up; |
|||
var bottomPosition = center + size.y * .5f * Vector3.down; |
|||
var frontPosition = center + size.z * .5f * Vector3.forward; |
|||
var backPosition = center + size.z * .5f * Vector3.back; |
|||
|
|||
var theChangedFace = NamedFace.None; |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
Slider1D(m_ControlIDs[(int)NamedFace.Left], ref leftPosition, Vector3.left, EditorSnapSettings.scale, GetHandleColor(NamedFace.Left)); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
theChangedFace = NamedFace.Left; |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
Slider1D(m_ControlIDs[(int)NamedFace.Right], ref rightPosition, Vector3.right, EditorSnapSettings.scale, GetHandleColor(NamedFace.Right)); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
theChangedFace = NamedFace.Right; |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
Slider1D(m_ControlIDs[(int)NamedFace.Top], ref topPosition, Vector3.up, EditorSnapSettings.scale, GetHandleColor(NamedFace.Top)); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
theChangedFace = NamedFace.Top; |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
Slider1D(m_ControlIDs[(int)NamedFace.Bottom], ref bottomPosition, Vector3.down, EditorSnapSettings.scale, GetHandleColor(NamedFace.Bottom)); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
theChangedFace = NamedFace.Bottom; |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
Slider1D(m_ControlIDs[(int)NamedFace.Front], ref frontPosition, Vector3.forward, EditorSnapSettings.scale, GetHandleColor(NamedFace.Front)); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
theChangedFace = NamedFace.Front; |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
Slider1D(m_ControlIDs[(int)NamedFace.Back], ref backPosition, Vector3.back, EditorSnapSettings.scale, GetHandleColor(NamedFace.Back)); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
theChangedFace = NamedFace.Back; |
|||
|
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
float delta = 0f; |
|||
switch (theChangedFace) |
|||
{ |
|||
case NamedFace.Left: delta = (leftPosition - center - size.x * .5f * Vector3.left).x; break; |
|||
case NamedFace.Right: delta = -(rightPosition - center - size.x * .5f * Vector3.right).x; break; |
|||
case NamedFace.Top: delta = -(topPosition - center - size.y * .5f * Vector3.up).y; break; |
|||
case NamedFace.Bottom: delta = (bottomPosition - center - size.y * .5f * Vector3.down).y; break; |
|||
case NamedFace.Front: delta = -(frontPosition - center - size.z * .5f * Vector3.forward).z; break; |
|||
case NamedFace.Back: delta = (backPosition - center - size.z * .5f * Vector3.back).z; break; |
|||
} |
|||
|
|||
if (monoHandle || useHomothety && useSymetry) |
|||
{ |
|||
var tempSize = size - Vector3.one * delta; |
|||
|
|||
//ensure that the box face are still facing outside
|
|||
for (int axis = 0; axis < 3; ++axis) |
|||
{ |
|||
if (tempSize[axis] < 0) |
|||
{ |
|||
delta += tempSize[axis]; |
|||
tempSize = size - Vector3.one * delta; |
|||
} |
|||
} |
|||
|
|||
//ensure containedBox do not exit container
|
|||
if (m_Parent != null) |
|||
{ |
|||
for (int axis = 0; axis < 3; ++axis) |
|||
{ |
|||
if (tempSize[axis] > m_Parent.size[axis]) |
|||
tempSize[axis] = m_Parent.size[axis]; |
|||
} |
|||
} |
|||
|
|||
size = tempSize; |
|||
} |
|||
else |
|||
{ |
|||
if (useSymetry) |
|||
{ |
|||
switch (theChangedFace) |
|||
{ |
|||
case NamedFace.Left: rightPosition.x -= delta; break; |
|||
case NamedFace.Right: leftPosition.x += delta; break; |
|||
case NamedFace.Top: bottomPosition.y += delta; break; |
|||
case NamedFace.Bottom: topPosition.y -= delta; break; |
|||
case NamedFace.Front: backPosition.z += delta; break; |
|||
case NamedFace.Back: frontPosition.z -= delta; break; |
|||
} |
|||
} |
|||
|
|||
if (useHomothety) |
|||
{ |
|||
float halfDelta = delta * 0.5f; |
|||
switch (theChangedFace) |
|||
{ |
|||
case NamedFace.Left: |
|||
case NamedFace.Right: |
|||
bottomPosition.y += halfDelta; |
|||
topPosition.y -= halfDelta; |
|||
backPosition.z += halfDelta; |
|||
frontPosition.z -= halfDelta; |
|||
break; |
|||
case NamedFace.Top: |
|||
case NamedFace.Bottom: |
|||
rightPosition.x -= halfDelta; |
|||
leftPosition.x += halfDelta; |
|||
backPosition.z += halfDelta; |
|||
frontPosition.z -= halfDelta; |
|||
break; |
|||
case NamedFace.Front: |
|||
case NamedFace.Back: |
|||
rightPosition.x -= halfDelta; |
|||
leftPosition.x += halfDelta; |
|||
bottomPosition.y += halfDelta; |
|||
topPosition.y -= halfDelta; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
var max = new Vector3(rightPosition.x, topPosition.y, frontPosition.z); |
|||
var min = new Vector3(leftPosition.x, bottomPosition.y, backPosition.z); |
|||
|
|||
//ensure that the box face are still facing outside
|
|||
for (int axis = 0; axis < 3; ++axis) |
|||
{ |
|||
if (min[axis] > max[axis]) |
|||
{ |
|||
// Control IDs in m_ControlIDs[0-3[ are for positive axes
|
|||
if (GUIUtility.hotControl == m_ControlIDs[axis]) |
|||
max[axis] = min[axis]; |
|||
else |
|||
min[axis] = max[axis]; |
|||
} |
|||
} |
|||
|
|||
//ensure containedBox do not exit container
|
|||
if (m_Parent != null) |
|||
{ |
|||
for (int axis = 0; axis < 3; ++axis) |
|||
{ |
|||
if (min[axis] < m_Parent.center[axis] - m_Parent.size[axis] * 0.5f) |
|||
min[axis] = m_Parent.center[axis] - m_Parent.size[axis] * 0.5f; |
|||
if (max[axis] > m_Parent.center[axis] + m_Parent.size[axis] * 0.5f) |
|||
max[axis] = m_Parent.center[axis] + m_Parent.size[axis] * 0.5f; |
|||
} |
|||
} |
|||
|
|||
center = (max + min) * .5f; |
|||
size = max - min; |
|||
} |
|||
} |
|||
} |
|||
|
|||
Color GetHandleColor(NamedFace name) => monoHandle ? m_MonochromeHandleColor : m_PolychromeHandleColor[(int)name]; |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 9c0adda657f204c4199b6870832abb80 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
/// <summary>
|
|||
/// Provide a gizmo/handle representing a box where all face can be moved independently.
|
|||
/// Also add a contained sub gizmo/handle box if contained is used at creation.
|
|||
/// </summary>
|
|||
/// <example>
|
|||
/// <code>
|
|||
/// class MyComponentEditor : Editor
|
|||
/// {
|
|||
/// static HierarchicalSphere sphere;
|
|||
/// static HierarchicalSphere containedSphere;
|
|||
///
|
|||
/// static MyComponentEditor()
|
|||
/// {
|
|||
/// Color[] handleColors = new Color[]
|
|||
/// {
|
|||
/// Color.red,
|
|||
/// Color.green,
|
|||
/// Color.Blue,
|
|||
/// new Color(0.5f, 0f, 0f, 1f),
|
|||
/// new Color(0f, 0.5f, 0f, 1f),
|
|||
/// new Color(0f, 0f, 0.5f, 1f)
|
|||
/// };
|
|||
/// sphere = new HierarchicalSphere(new Color(1f, 1f, 1f, 0.25));
|
|||
/// containedSphere = new HierarchicalSphere(new Color(1f, 0f, 1f, 0.25), container: sphere);
|
|||
/// }
|
|||
///
|
|||
/// [DrawGizmo(GizmoType.Selected|GizmoType.Active)]
|
|||
/// void DrawGizmo(MyComponent comp, GizmoType gizmoType)
|
|||
/// {
|
|||
/// sphere.center = comp.transform.position;
|
|||
/// sphere.size = comp.transform.scale;
|
|||
/// sphere.DrawHull(gizmoType == GizmoType.Selected);
|
|||
///
|
|||
/// containedSphere.center = comp.innerposition;
|
|||
/// containedSphere.size = comp.innerScale;
|
|||
/// containedSphere.DrawHull(gizmoType == GizmoType.Selected);
|
|||
/// }
|
|||
///
|
|||
/// void OnSceneGUI()
|
|||
/// {
|
|||
/// EditorGUI.BeginChangeCheck();
|
|||
///
|
|||
/// //container sphere must be also set for contained sphere for clamping
|
|||
/// sphere.center = comp.transform.position;
|
|||
/// sphere.size = comp.transform.scale;
|
|||
/// sphere.DrawHandle();
|
|||
///
|
|||
/// containedSphere.center = comp.innerposition;
|
|||
/// containedSphere.size = comp.innerScale;
|
|||
/// containedSphere.DrawHandle();
|
|||
///
|
|||
/// if(EditorGUI.EndChangeCheck())
|
|||
/// {
|
|||
/// comp.innerposition = containedSphere.center;
|
|||
/// comp.innersize = containedSphere.size;
|
|||
/// }
|
|||
/// }
|
|||
/// }
|
|||
/// </code>
|
|||
/// </example>
|
|||
public class HierarchicalSphere |
|||
{ |
|||
const float k_HandleSizeCoef = 0.05f; |
|||
|
|||
static Material k_Material_Cache; |
|||
static Material k_Material => (k_Material_Cache == null || k_Material_Cache.Equals(null) ? (k_Material_Cache = new Material(Shader.Find("Hidden/UnlitTransparentColored"))) : k_Material_Cache); |
|||
static Mesh k_MeshSphere_Cache; |
|||
static Mesh k_MeshSphere => k_MeshSphere_Cache == null || k_MeshSphere_Cache.Equals(null) ? (k_MeshSphere_Cache = Resources.GetBuiltinResource<Mesh>("New-Sphere.fbx")) : k_MeshSphere_Cache; |
|||
|
|||
Material m_Material; |
|||
readonly HierarchicalSphere m_Parent; |
|||
Color m_HandleColor; |
|||
Color m_WireframeColor; |
|||
Color m_WireframeColorBehind; |
|||
|
|||
Material material => m_Material == null || m_Material.Equals(null) |
|||
? (m_Material = new Material(k_Material)) |
|||
: m_Material; |
|||
|
|||
/// <summary>The position of the center of the box in Handle.matrix space.</summary>
|
|||
public Vector3 center { get; set; } |
|||
|
|||
/// <summary>The size of the box in Handle.matrix space.</summary>
|
|||
public float radius { get; set; } |
|||
|
|||
/// <summary>The baseColor used to fill hull. All other colors are deduced from it.</summary>
|
|||
public Color baseColor |
|||
{ |
|||
get { return material.color; } |
|||
set |
|||
{ |
|||
value.a = 8f / 255; |
|||
material.color = value; |
|||
value.a = 1f; |
|||
m_HandleColor = value; |
|||
value.a = 0.7f; |
|||
m_WireframeColor = value; |
|||
value.a = 0.2f; |
|||
m_WireframeColorBehind = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>Constructor. Used to setup colors and also the container if any.</summary>
|
|||
/// <param name="baseColor">The color of filling. All other colors are deduced from it.</param>
|
|||
/// <param name="parent">The HierarchicalSphere containing this sphere. If null, the sphere will not be limited in size.</param>
|
|||
public HierarchicalSphere(Color baseColor, HierarchicalSphere parent = null) |
|||
{ |
|||
m_Parent = parent; |
|||
m_Material = new Material(k_Material); |
|||
this.baseColor = baseColor; |
|||
} |
|||
|
|||
/// <summary>Draw the hull which means the boxes without the handles</summary>
|
|||
public void DrawHull(bool filled) |
|||
{ |
|||
Color wireframeColor = m_HandleColor; |
|||
wireframeColor.a = 0.8f; |
|||
using (new Handles.DrawingScope(m_WireframeColor, Matrix4x4.TRS((Vector3)Handles.matrix.GetColumn(3) + center, Quaternion.identity, Vector3.one))) |
|||
{ |
|||
if (filled) |
|||
{ |
|||
material.SetPass(0); |
|||
Matrix4x4 drawMatrix = Matrix4x4.TRS((Vector3)Handles.matrix.GetColumn(3), Quaternion.identity, Vector3.one * radius * 2f); |
|||
Graphics.DrawMeshNow(k_MeshSphere, drawMatrix); |
|||
} |
|||
|
|||
var drawCenter = Vector3.zero; |
|||
var viewPlaneNormal = Vector3.zero; |
|||
var drawnRadius = radius; |
|||
if (Camera.current.orthographic) |
|||
viewPlaneNormal = Camera.current.transform.forward; |
|||
else |
|||
{ |
|||
viewPlaneNormal = (Vector3)Handles.matrix.GetColumn(3) - Camera.current.transform.position; |
|||
var sqrDist = viewPlaneNormal.sqrMagnitude; // squared distance from camera to center
|
|||
var sqrRadius = radius * radius; // squared radius
|
|||
var sqrOffset = sqrRadius * sqrRadius / sqrDist; // squared distance from actual center to drawn disc center
|
|||
var insideAmount = sqrOffset / sqrRadius; |
|||
|
|||
// If we are not inside the sphere, calculate where to draw the periphery
|
|||
if (insideAmount < 1) |
|||
{ |
|||
drawnRadius = Mathf.Sqrt(sqrRadius - sqrOffset); // the radius of the drawn disc
|
|||
drawCenter -= (sqrRadius / sqrDist) * viewPlaneNormal; |
|||
} |
|||
} |
|||
|
|||
Handles.zTest = UnityEngine.Rendering.CompareFunction.LessEqual; |
|||
Handles.DrawWireDisc(Vector3.zero, Vector3.up, radius); |
|||
Handles.DrawWireDisc(drawCenter, viewPlaneNormal, drawnRadius); |
|||
|
|||
Handles.color = m_WireframeColorBehind; |
|||
Handles.zTest = UnityEngine.Rendering.CompareFunction.Greater; |
|||
Handles.DrawWireDisc(Vector3.zero, Vector3.up, radius); |
|||
Handles.DrawWireDisc(drawCenter, viewPlaneNormal, drawnRadius); |
|||
Handles.zTest = UnityEngine.Rendering.CompareFunction.Always; |
|||
} |
|||
} |
|||
|
|||
/// <summary>Draw the manipulable handles</summary>
|
|||
public void DrawHandle() |
|||
{ |
|||
using (new Handles.DrawingScope(m_HandleColor)) |
|||
{ |
|||
radius = Handles.RadiusHandle(Quaternion.identity, center, radius, handlesOnly: true); |
|||
if(m_Parent != null) |
|||
radius = Mathf.Min(radius, m_Parent.radius - Vector3.Distance(center, m_Parent.center)); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 7ecd4b4b0c2b54742baf8d885609f3b9 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
Shader "Hidden/UnlitTransparentColored" { |
|||
Properties { |
|||
_Color ("Main Color", Color) = (1,1,1,1) |
|||
} |
|||
|
|||
SubShader { |
|||
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"} |
|||
|
|||
ZWrite Off |
|||
Lighting Off |
|||
Fog { Mode Off } |
|||
Cull Off |
|||
|
|||
Blend SrcAlpha OneMinusSrcAlpha |
|||
|
|||
Pass { |
|||
Color [_Color] |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 2df96b66b5510f94b98df2ac6f926ef4 |
|||
ShaderImporter: |
|||
externalObjects: {} |
|||
defaultTextures: [] |
|||
nonModifiableTextures: [] |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
// A good chunk of this class is recycled from Post-processing v2 and could use a cleaning pass
|
|||
public sealed class InspectorCurveEditor |
|||
{ |
|||
enum EditMode |
|||
{ |
|||
None, |
|||
Moving, |
|||
TangentEdit |
|||
} |
|||
|
|||
enum Tangent |
|||
{ |
|||
In, |
|||
Out |
|||
} |
|||
|
|||
public struct Settings |
|||
{ |
|||
public Rect bounds; |
|||
public RectOffset padding; |
|||
public Color selectionColor; |
|||
public float curvePickingDistance; |
|||
public float keyTimeClampingDistance; |
|||
|
|||
public static Settings defaultSettings => new Settings |
|||
{ |
|||
bounds = new Rect(0f, 0f, 1f, 1f), |
|||
padding = new RectOffset(0, 0, 0, 0), |
|||
selectionColor = Color.yellow, |
|||
curvePickingDistance = 6f, |
|||
keyTimeClampingDistance = 1e-4f |
|||
}; |
|||
} |
|||
|
|||
public struct CurveState |
|||
{ |
|||
public bool visible; |
|||
public bool editable; |
|||
public uint minPointCount; |
|||
public float zeroKeyConstantValue; |
|||
public Color color; |
|||
public float width; |
|||
public float handleWidth; |
|||
public bool showNonEditableHandles; |
|||
public bool onlyShowHandlesOnSelection; |
|||
public bool loopInBounds; |
|||
|
|||
public static CurveState defaultState => new CurveState |
|||
{ |
|||
visible = true, |
|||
editable = true, |
|||
minPointCount = 2, |
|||
zeroKeyConstantValue = 0f, |
|||
color = Color.white, |
|||
width = 2f, |
|||
handleWidth = 2f, |
|||
showNonEditableHandles = true, |
|||
onlyShowHandlesOnSelection = false, |
|||
loopInBounds = false |
|||
}; |
|||
} |
|||
|
|||
public struct Selection |
|||
{ |
|||
public SerializedProperty curve; |
|||
public int keyframeIndex; |
|||
public Keyframe? keyframe; |
|||
|
|||
public Selection(SerializedProperty curve, int keyframeIndex, Keyframe? keyframe) |
|||
{ |
|||
this.curve = curve; |
|||
this.keyframeIndex = keyframeIndex; |
|||
this.keyframe = keyframe; |
|||
} |
|||
} |
|||
|
|||
internal struct MenuAction |
|||
{ |
|||
internal SerializedProperty curve; |
|||
internal int index; |
|||
internal Vector3 position; |
|||
|
|||
internal MenuAction(SerializedProperty curve) |
|||
{ |
|||
this.curve = curve; |
|||
this.index = -1; |
|||
this.position = Vector3.zero; |
|||
} |
|||
|
|||
internal MenuAction(SerializedProperty curve, int index) |
|||
{ |
|||
this.curve = curve; |
|||
this.index = index; |
|||
this.position = Vector3.zero; |
|||
} |
|||
|
|||
internal MenuAction(SerializedProperty curve, Vector3 position) |
|||
{ |
|||
this.curve = curve; |
|||
this.index = -1; |
|||
this.position = position; |
|||
} |
|||
} |
|||
|
|||
public readonly Settings settings; |
|||
|
|||
readonly Dictionary<SerializedProperty, CurveState> m_Curves; |
|||
Rect m_CurveArea; |
|||
|
|||
SerializedProperty m_SelectedCurve; |
|||
int m_SelectedKeyframeIndex = -1; |
|||
|
|||
EditMode m_EditMode = EditMode.None; |
|||
Tangent m_TangentEditMode; |
|||
|
|||
bool m_Dirty; |
|||
|
|||
public InspectorCurveEditor() |
|||
: this(Settings.defaultSettings) { } |
|||
|
|||
public InspectorCurveEditor(Settings settings) |
|||
{ |
|||
this.settings = settings; |
|||
m_Curves = new Dictionary<SerializedProperty, CurveState>(); |
|||
} |
|||
|
|||
public void Add(params SerializedProperty[] curves) |
|||
{ |
|||
foreach (var curve in curves) |
|||
Add(curve, CurveState.defaultState); |
|||
} |
|||
|
|||
public void Add(SerializedProperty curve) |
|||
{ |
|||
Add(curve, CurveState.defaultState); |
|||
} |
|||
|
|||
public void Add(SerializedProperty curve, CurveState state) |
|||
{ |
|||
// Make sure the property is in fact an AnimationCurve
|
|||
var animCurve = curve.animationCurveValue; |
|||
if (animCurve == null) |
|||
throw new ArgumentException("curve"); |
|||
|
|||
if (m_Curves.ContainsKey(curve)) |
|||
Debug.LogWarning("Curve has already been added to the editor"); |
|||
|
|||
m_Curves.Add(curve, state); |
|||
} |
|||
|
|||
public void Remove(SerializedProperty curve) |
|||
{ |
|||
m_Curves.Remove(curve); |
|||
} |
|||
|
|||
public void RemoveAll() |
|||
{ |
|||
m_Curves.Clear(); |
|||
} |
|||
|
|||
public CurveState GetCurveState(SerializedProperty curve) |
|||
{ |
|||
if (!m_Curves.TryGetValue(curve, out var state)) |
|||
throw new KeyNotFoundException("curve"); |
|||
|
|||
return state; |
|||
} |
|||
|
|||
public void SetCurveState(SerializedProperty curve, CurveState state) |
|||
{ |
|||
if (!m_Curves.ContainsKey(curve)) |
|||
throw new KeyNotFoundException("curve"); |
|||
|
|||
m_Curves[curve] = state; |
|||
} |
|||
|
|||
public Selection GetSelection() |
|||
{ |
|||
Keyframe? key = null; |
|||
if (m_SelectedKeyframeIndex > -1) |
|||
{ |
|||
var curve = m_SelectedCurve.animationCurveValue; |
|||
|
|||
if (m_SelectedKeyframeIndex >= curve.length) |
|||
m_SelectedKeyframeIndex = -1; |
|||
else |
|||
key = curve[m_SelectedKeyframeIndex]; |
|||
} |
|||
|
|||
return new Selection(m_SelectedCurve, m_SelectedKeyframeIndex, key); |
|||
} |
|||
|
|||
public void SetKeyframe(SerializedProperty curve, int keyframeIndex, Keyframe keyframe) |
|||
{ |
|||
var animCurve = curve.animationCurveValue; |
|||
SetKeyframe(animCurve, keyframeIndex, keyframe); |
|||
SaveCurve(curve, animCurve); |
|||
} |
|||
|
|||
public bool OnGUI(Rect rect) |
|||
{ |
|||
if (Event.current.type == EventType.Repaint) |
|||
m_Dirty = false; |
|||
|
|||
GUI.BeginClip(rect); |
|||
{ |
|||
var area = new Rect(Vector2.zero, rect.size); |
|||
m_CurveArea = settings.padding.Remove(area); |
|||
|
|||
foreach (var curve in m_Curves) |
|||
OnCurveGUI(area, curve.Key, curve.Value); |
|||
|
|||
OnGeneralUI(); |
|||
} |
|||
GUI.EndClip(); |
|||
|
|||
return m_Dirty; |
|||
} |
|||
|
|||
void OnCurveGUI(Rect rect, SerializedProperty curve, CurveState state) |
|||
{ |
|||
// Discard invisible curves
|
|||
if (!state.visible) |
|||
return; |
|||
|
|||
var animCurve = curve.animationCurveValue; |
|||
var keys = animCurve.keys; |
|||
var length = keys.Length; |
|||
|
|||
// Curve drawing
|
|||
// Slightly dim non-editable curves
|
|||
var color = state.color; |
|||
if (!state.editable || !GUI.enabled) |
|||
color.a *= 0.5f; |
|||
|
|||
Handles.color = color; |
|||
var bounds = settings.bounds; |
|||
|
|||
if (length == 0) |
|||
{ |
|||
var p1 = CurveToCanvas(new Vector3(bounds.xMin, state.zeroKeyConstantValue)); |
|||
var p2 = CurveToCanvas(new Vector3(bounds.xMax, state.zeroKeyConstantValue)); |
|||
Handles.DrawAAPolyLine(state.width, p1, p2); |
|||
} |
|||
else if (length == 1) |
|||
{ |
|||
var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value)); |
|||
var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[0].value)); |
|||
Handles.DrawAAPolyLine(state.width, p1, p2); |
|||
} |
|||
else |
|||
{ |
|||
var prevKey = keys[0]; |
|||
for (int k = 1; k < length; k++) |
|||
{ |
|||
var key = keys[k]; |
|||
var pts = BezierSegment(prevKey, key); |
|||
|
|||
if (float.IsInfinity(prevKey.outTangent) || float.IsInfinity(key.inTangent)) |
|||
{ |
|||
var s = HardSegment(prevKey, key); |
|||
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]); |
|||
} |
|||
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width); |
|||
|
|||
prevKey = key; |
|||
} |
|||
|
|||
// Curve extents & loops
|
|||
if (keys[0].time > bounds.xMin) |
|||
{ |
|||
if (state.loopInBounds) |
|||
{ |
|||
var p1 = keys[length - 1]; |
|||
p1.time -= settings.bounds.width; |
|||
var p2 = keys[0]; |
|||
var pts = BezierSegment(p1, p2); |
|||
|
|||
if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent)) |
|||
{ |
|||
var s = HardSegment(p1, p2); |
|||
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]); |
|||
} |
|||
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width); |
|||
} |
|||
else |
|||
{ |
|||
var p1 = CurveToCanvas(new Vector3(bounds.xMin, keys[0].value)); |
|||
var p2 = CurveToCanvas(keys[0]); |
|||
Handles.DrawAAPolyLine(state.width, p1, p2); |
|||
} |
|||
} |
|||
|
|||
if (keys[length - 1].time < bounds.xMax) |
|||
{ |
|||
if (state.loopInBounds) |
|||
{ |
|||
var p1 = keys[length - 1]; |
|||
var p2 = keys[0]; |
|||
p2.time += settings.bounds.width; |
|||
var pts = BezierSegment(p1, p2); |
|||
|
|||
if (float.IsInfinity(p1.outTangent) || float.IsInfinity(p2.inTangent)) |
|||
{ |
|||
var s = HardSegment(p1, p2); |
|||
Handles.DrawAAPolyLine(state.width, s[0], s[1], s[2]); |
|||
} |
|||
else Handles.DrawBezier(pts[0], pts[3], pts[1], pts[2], color, null, state.width); |
|||
} |
|||
else |
|||
{ |
|||
var p1 = CurveToCanvas(keys[length - 1]); |
|||
var p2 = CurveToCanvas(new Vector3(bounds.xMax, keys[length - 1].value)); |
|||
Handles.DrawAAPolyLine(state.width, p1, p2); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Make sure selection is correct (undo can break it)
|
|||
bool isCurrentlySelectedCurve = curve == m_SelectedCurve; |
|||
|
|||
if (isCurrentlySelectedCurve && m_SelectedKeyframeIndex >= length) |
|||
m_SelectedKeyframeIndex = -1; |
|||
|
|||
if (!state.editable) |
|||
m_SelectedKeyframeIndex = -1; |
|||
|
|||
float enabledFactor = GUI.enabled ? 1f : 0.8f; |
|||
|
|||
// Handles & keys
|
|||
for (int k = 0; k < length; k++) |
|||
{ |
|||
bool isCurrentlySelectedKeyframe = k == m_SelectedKeyframeIndex; |
|||
var e = Event.current; |
|||
|
|||
var pos = CurveToCanvas(keys[k]); |
|||
var hitRect = new Rect(pos.x - 8f, pos.y - 8f, 16f, 16f); |
|||
var offset = isCurrentlySelectedCurve |
|||
? new RectOffset(5, 5, 5, 5) |
|||
: new RectOffset(6, 6, 6, 6); |
|||
|
|||
var outTangent = pos + CurveTangentToCanvas(keys[k].outTangent).normalized * 40f; |
|||
var inTangent = pos - CurveTangentToCanvas(keys[k].inTangent).normalized * 40f; |
|||
var inTangentHitRect = new Rect(inTangent.x - 7f, inTangent.y - 7f, 14f, 14f); |
|||
var outTangentHitrect = new Rect(outTangent.x - 7f, outTangent.y - 7f, 14f, 14f); |
|||
|
|||
// Draw
|
|||
if (state.editable || state.showNonEditableHandles) |
|||
{ |
|||
if (e.type == EventType.Repaint) |
|||
{ |
|||
var selectedColor = (isCurrentlySelectedCurve && isCurrentlySelectedKeyframe) |
|||
? settings.selectionColor |
|||
: state.color; |
|||
|
|||
// Keyframe
|
|||
EditorGUI.DrawRect(offset.Remove(hitRect), selectedColor * enabledFactor); |
|||
|
|||
// Tangents
|
|||
if (length > 1 && isCurrentlySelectedCurve && (!state.onlyShowHandlesOnSelection || (state.onlyShowHandlesOnSelection && isCurrentlySelectedKeyframe))) |
|||
{ |
|||
Handles.color = selectedColor * enabledFactor; |
|||
|
|||
if (k > 0 || state.loopInBounds) |
|||
{ |
|||
Handles.DrawAAPolyLine(state.handleWidth, pos, inTangent); |
|||
EditorGUI.DrawRect(offset.Remove(inTangentHitRect), selectedColor); |
|||
} |
|||
|
|||
if (k < length - 1 || state.loopInBounds) |
|||
{ |
|||
Handles.DrawAAPolyLine(state.handleWidth, pos, outTangent); |
|||
EditorGUI.DrawRect(offset.Remove(outTangentHitrect), selectedColor); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Events
|
|||
if (state.editable) |
|||
{ |
|||
// Keyframe move
|
|||
if (m_EditMode == EditMode.Moving && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe) |
|||
{ |
|||
EditMoveKeyframe(animCurve, keys, k); |
|||
} |
|||
|
|||
// Tangent editing
|
|||
if (length > 1 && m_EditMode == EditMode.TangentEdit && e.type == EventType.MouseDrag && isCurrentlySelectedCurve && isCurrentlySelectedKeyframe) |
|||
{ |
|||
bool alreadyBroken = !(Mathf.Approximately(keys[k].inTangent, keys[k].outTangent) || (float.IsInfinity(keys[k].inTangent) && float.IsInfinity(keys[k].outTangent))); |
|||
EditMoveTangent(animCurve, keys, k, m_TangentEditMode, e.shift || !(alreadyBroken || e.control)); |
|||
} |
|||
|
|||
// Keyframe selection & context menu
|
|||
if (e.type == EventType.MouseDown && rect.Contains(e.mousePosition)) |
|||
{ |
|||
if (hitRect.Contains(e.mousePosition)) |
|||
{ |
|||
if (e.button == 0) |
|||
{ |
|||
SelectKeyframe(curve, k); |
|||
m_EditMode = EditMode.Moving; |
|||
e.Use(); |
|||
} |
|||
else if (e.button == 1) |
|||
{ |
|||
// Keyframe context menu
|
|||
var menu = new GenericMenu(); |
|||
menu.AddItem(new GUIContent("Delete Key"), false, (x) => |
|||
{ |
|||
var action = (MenuAction)x; |
|||
var curveValue = action.curve.animationCurveValue; |
|||
action.curve.serializedObject.Update(); |
|||
RemoveKeyframe(curveValue, action.index); |
|||
m_SelectedKeyframeIndex = -1; |
|||
SaveCurve(action.curve, curveValue); |
|||
action.curve.serializedObject.ApplyModifiedProperties(); |
|||
}, new MenuAction(curve, k)); |
|||
menu.ShowAsContext(); |
|||
e.Use(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Tangent selection & edit mode
|
|||
if (e.type == EventType.MouseDown && length > 1 && rect.Contains(e.mousePosition)) |
|||
{ |
|||
if (inTangentHitRect.Contains(e.mousePosition) && (k > 0 || state.loopInBounds)) |
|||
{ |
|||
SelectKeyframe(curve, k); |
|||
m_EditMode = EditMode.TangentEdit; |
|||
m_TangentEditMode = Tangent.In; |
|||
e.Use(); |
|||
} |
|||
else if (outTangentHitrect.Contains(e.mousePosition) && (k < length - 1 || state.loopInBounds)) |
|||
{ |
|||
SelectKeyframe(curve, k); |
|||
m_EditMode = EditMode.TangentEdit; |
|||
m_TangentEditMode = Tangent.Out; |
|||
e.Use(); |
|||
} |
|||
} |
|||
|
|||
// Mouse up - clean up states
|
|||
if (e.rawType == EventType.MouseUp && m_EditMode != EditMode.None) |
|||
{ |
|||
m_EditMode = EditMode.None; |
|||
} |
|||
|
|||
// Set cursors
|
|||
{ |
|||
EditorGUIUtility.AddCursorRect(hitRect, MouseCursor.MoveArrow); |
|||
|
|||
if (k > 0 || state.loopInBounds) |
|||
EditorGUIUtility.AddCursorRect(inTangentHitRect, MouseCursor.RotateArrow); |
|||
|
|||
if (k < length - 1 || state.loopInBounds) |
|||
EditorGUIUtility.AddCursorRect(outTangentHitrect, MouseCursor.RotateArrow); |
|||
} |
|||
} |
|||
} |
|||
|
|||
Handles.color = Color.white; |
|||
SaveCurve(curve, animCurve); |
|||
} |
|||
|
|||
void OnGeneralUI() |
|||
{ |
|||
var e = Event.current; |
|||
|
|||
// Selection
|
|||
if (e.type == EventType.MouseDown) |
|||
{ |
|||
GUI.FocusControl(null); |
|||
m_SelectedCurve = null; |
|||
m_SelectedKeyframeIndex = -1; |
|||
bool used = false; |
|||
|
|||
var hit = CanvasToCurve(e.mousePosition); |
|||
float curvePickValue = CurveToCanvas(hit).y; |
|||
|
|||
// Try and select a curve
|
|||
foreach (var curve in m_Curves) |
|||
{ |
|||
if (!curve.Value.editable || !curve.Value.visible) |
|||
continue; |
|||
|
|||
var prop = curve.Key; |
|||
var state = curve.Value; |
|||
var animCurve = prop.animationCurveValue; |
|||
float hitY = animCurve.length == 0 |
|||
? state.zeroKeyConstantValue |
|||
: animCurve.Evaluate(hit.x); |
|||
|
|||
var curvePos = CurveToCanvas(new Vector3(hit.x, hitY)); |
|||
|
|||
if (Mathf.Abs(curvePos.y - curvePickValue) < settings.curvePickingDistance) |
|||
{ |
|||
m_SelectedCurve = prop; |
|||
|
|||
if (e.clickCount == 2 && e.button == 0) |
|||
{ |
|||
// Create a keyframe on double-click on this curve
|
|||
EditCreateKeyframe(animCurve, hit, true, state.zeroKeyConstantValue); |
|||
SaveCurve(prop, animCurve); |
|||
} |
|||
else if (e.button == 1) |
|||
{ |
|||
// Curve context menu
|
|||
var menu = new GenericMenu(); |
|||
menu.AddItem(new GUIContent("Add Key"), false, (x) => |
|||
{ |
|||
var action = (MenuAction)x; |
|||
var curveValue = action.curve.animationCurveValue; |
|||
action.curve.serializedObject.Update(); |
|||
EditCreateKeyframe(curveValue, hit, true, 0f); |
|||
SaveCurve(action.curve, curveValue); |
|||
action.curve.serializedObject.ApplyModifiedProperties(); |
|||
}, new MenuAction(prop, hit)); |
|||
menu.ShowAsContext(); |
|||
e.Use(); |
|||
used = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (e.clickCount == 2 && e.button == 0 && m_SelectedCurve == null) |
|||
{ |
|||
// Create a keyframe on every curve on double-click
|
|||
foreach (var curve in m_Curves) |
|||
{ |
|||
if (!curve.Value.editable || !curve.Value.visible) |
|||
continue; |
|||
|
|||
var prop = curve.Key; |
|||
var state = curve.Value; |
|||
var animCurve = prop.animationCurveValue; |
|||
EditCreateKeyframe(animCurve, hit, e.alt, state.zeroKeyConstantValue); |
|||
SaveCurve(prop, animCurve); |
|||
} |
|||
} |
|||
else if (!used && e.button == 1) |
|||
{ |
|||
// Global context menu
|
|||
var menu = new GenericMenu(); |
|||
menu.AddItem(new GUIContent("Add Key At Position"), false, () => ContextMenuAddKey(hit, false)); |
|||
menu.AddItem(new GUIContent("Add Key On Curves"), false, () => ContextMenuAddKey(hit, true)); |
|||
menu.ShowAsContext(); |
|||
} |
|||
|
|||
e.Use(); |
|||
} |
|||
|
|||
// Delete selected key(s)
|
|||
if (e.type == EventType.KeyDown && (e.keyCode == KeyCode.Delete || e.keyCode == KeyCode.Backspace)) |
|||
{ |
|||
if (m_SelectedKeyframeIndex != -1 && m_SelectedCurve != null) |
|||
{ |
|||
var animCurve = m_SelectedCurve.animationCurveValue; |
|||
var length = animCurve.length; |
|||
|
|||
if (m_Curves[m_SelectedCurve].minPointCount < length && length >= 0) |
|||
{ |
|||
EditDeleteKeyframe(animCurve, m_SelectedKeyframeIndex); |
|||
m_SelectedKeyframeIndex = -1; |
|||
SaveCurve(m_SelectedCurve, animCurve); |
|||
} |
|||
|
|||
e.Use(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
void SaveCurve(SerializedProperty prop, AnimationCurve curve) |
|||
{ |
|||
prop.animationCurveValue = curve; |
|||
} |
|||
|
|||
void Invalidate() |
|||
{ |
|||
m_Dirty = true; |
|||
} |
|||
|
|||
void SelectKeyframe(SerializedProperty curve, int keyframeIndex) |
|||
{ |
|||
m_SelectedKeyframeIndex = keyframeIndex; |
|||
m_SelectedCurve = curve; |
|||
Invalidate(); |
|||
} |
|||
|
|||
void ContextMenuAddKey(Vector3 hit, bool createOnCurve) |
|||
{ |
|||
SerializedObject serializedObject = null; |
|||
|
|||
foreach (var curve in m_Curves) |
|||
{ |
|||
if (!curve.Value.editable || !curve.Value.visible) |
|||
continue; |
|||
|
|||
var prop = curve.Key; |
|||
var state = curve.Value; |
|||
|
|||
if (serializedObject == null) |
|||
{ |
|||
serializedObject = prop.serializedObject; |
|||
serializedObject.Update(); |
|||
} |
|||
|
|||
var animCurve = prop.animationCurveValue; |
|||
EditCreateKeyframe(animCurve, hit, createOnCurve, state.zeroKeyConstantValue); |
|||
SaveCurve(prop, animCurve); |
|||
} |
|||
|
|||
if (serializedObject != null) |
|||
serializedObject.ApplyModifiedProperties(); |
|||
|
|||
Invalidate(); |
|||
} |
|||
|
|||
void EditCreateKeyframe(AnimationCurve curve, Vector3 position, bool createOnCurve, float zeroKeyConstantValue) |
|||
{ |
|||
float tangent = EvaluateTangent(curve, position.x); |
|||
|
|||
if (createOnCurve) |
|||
{ |
|||
position.y = curve.length == 0 |
|||
? zeroKeyConstantValue |
|||
: curve.Evaluate(position.x); |
|||
} |
|||
|
|||
AddKeyframe(curve, new Keyframe(position.x, position.y, tangent, tangent)); |
|||
} |
|||
|
|||
void EditDeleteKeyframe(AnimationCurve curve, int keyframeIndex) |
|||
{ |
|||
RemoveKeyframe(curve, keyframeIndex); |
|||
} |
|||
|
|||
void AddKeyframe(AnimationCurve curve, Keyframe newValue) |
|||
{ |
|||
curve.AddKey(newValue); |
|||
Invalidate(); |
|||
} |
|||
|
|||
void RemoveKeyframe(AnimationCurve curve, int keyframeIndex) |
|||
{ |
|||
curve.RemoveKey(keyframeIndex); |
|||
Invalidate(); |
|||
} |
|||
|
|||
void SetKeyframe(AnimationCurve curve, int keyframeIndex, Keyframe newValue) |
|||
{ |
|||
var keys = curve.keys; |
|||
|
|||
if (keyframeIndex > 0) |
|||
newValue.time = Mathf.Max(keys[keyframeIndex - 1].time + settings.keyTimeClampingDistance, newValue.time); |
|||
|
|||
if (keyframeIndex < keys.Length - 1) |
|||
newValue.time = Mathf.Min(keys[keyframeIndex + 1].time - settings.keyTimeClampingDistance, newValue.time); |
|||
|
|||
curve.MoveKey(keyframeIndex, newValue); |
|||
Invalidate(); |
|||
} |
|||
|
|||
void EditMoveKeyframe(AnimationCurve curve, Keyframe[] keys, int keyframeIndex) |
|||
{ |
|||
var key = CanvasToCurve(Event.current.mousePosition); |
|||
float inTgt = keys[keyframeIndex].inTangent; |
|||
float outTgt = keys[keyframeIndex].outTangent; |
|||
SetKeyframe(curve, keyframeIndex, new Keyframe(key.x, key.y, inTgt, outTgt)); |
|||
} |
|||
|
|||
void EditMoveTangent(AnimationCurve curve, Keyframe[] keys, int keyframeIndex, Tangent targetTangent, bool linkTangents) |
|||
{ |
|||
var pos = CanvasToCurve(Event.current.mousePosition); |
|||
|
|||
float time = keys[keyframeIndex].time; |
|||
float value = keys[keyframeIndex].value; |
|||
|
|||
pos -= new Vector3(time, value); |
|||
|
|||
if (targetTangent == Tangent.In && pos.x > 0f) |
|||
pos.x = 0f; |
|||
|
|||
if (targetTangent == Tangent.Out && pos.x < 0f) |
|||
pos.x = 0f; |
|||
|
|||
float tangent; |
|||
|
|||
if (Mathf.Approximately(pos.x, 0f)) |
|||
tangent = float.PositiveInfinity; |
|||
else |
|||
tangent = pos.y / pos.x; |
|||
|
|||
float inTangent = keys[keyframeIndex].inTangent; |
|||
float outTangent = keys[keyframeIndex].outTangent; |
|||
|
|||
if (targetTangent == Tangent.In || linkTangents) |
|||
inTangent = tangent; |
|||
if (targetTangent == Tangent.Out || linkTangents) |
|||
outTangent = tangent; |
|||
|
|||
SetKeyframe(curve, keyframeIndex, new Keyframe(time, value, inTangent, outTangent)); |
|||
} |
|||
|
|||
Vector3 CurveToCanvas(Keyframe keyframe) |
|||
{ |
|||
return CurveToCanvas(new Vector3(keyframe.time, keyframe.value)); |
|||
} |
|||
|
|||
Vector3 CurveToCanvas(Vector3 position) |
|||
{ |
|||
var bounds = settings.bounds; |
|||
var output = new Vector3((position.x - bounds.x) / (bounds.xMax - bounds.x), (position.y - bounds.y) / (bounds.yMax - bounds.y)); |
|||
output.x = output.x * (m_CurveArea.xMax - m_CurveArea.xMin) + m_CurveArea.xMin; |
|||
output.y = (1f - output.y) * (m_CurveArea.yMax - m_CurveArea.yMin) + m_CurveArea.yMin; |
|||
return output; |
|||
} |
|||
|
|||
Vector3 CanvasToCurve(Vector3 position) |
|||
{ |
|||
var bounds = settings.bounds; |
|||
var output = position; |
|||
output.x = (output.x - m_CurveArea.xMin) / (m_CurveArea.xMax - m_CurveArea.xMin); |
|||
output.y = (output.y - m_CurveArea.yMin) / (m_CurveArea.yMax - m_CurveArea.yMin); |
|||
output.x = Mathf.Lerp(bounds.x, bounds.xMax, output.x); |
|||
output.y = Mathf.Lerp(bounds.yMax, bounds.y, output.y); |
|||
return output; |
|||
} |
|||
|
|||
Vector3 CurveTangentToCanvas(float tangent) |
|||
{ |
|||
if (!float.IsInfinity(tangent)) |
|||
{ |
|||
var bounds = settings.bounds; |
|||
float ratio = (m_CurveArea.width / m_CurveArea.height) / ((bounds.xMax - bounds.x) / (bounds.yMax - bounds.y)); |
|||
return new Vector3(1f, -tangent / ratio).normalized; |
|||
} |
|||
|
|||
return Vector3.up; // Positive infinity
|
|||
} |
|||
|
|||
Vector3[] BezierSegment(Keyframe start, Keyframe end) |
|||
{ |
|||
var segment = new Vector3[4]; |
|||
|
|||
segment[0] = CurveToCanvas(new Vector3(start.time, start.value)); |
|||
segment[3] = CurveToCanvas(new Vector3(end.time, end.value)); |
|||
|
|||
float middle = start.time + ((end.time - start.time) * 0.333333f); |
|||
float middle2 = start.time + ((end.time - start.time) * 0.666666f); |
|||
|
|||
segment[1] = CurveToCanvas(new Vector3(middle, ProjectTangent(start.time, start.value, start.outTangent, middle))); |
|||
segment[2] = CurveToCanvas(new Vector3(middle2, ProjectTangent(end.time, end.value, end.inTangent, middle2))); |
|||
|
|||
return segment; |
|||
} |
|||
|
|||
Vector3[] HardSegment(Keyframe start, Keyframe end) |
|||
{ |
|||
var segment = new Vector3[3]; |
|||
|
|||
segment[0] = CurveToCanvas(start); |
|||
segment[1] = CurveToCanvas(new Vector3(end.time, start.value)); |
|||
segment[2] = CurveToCanvas(end); |
|||
|
|||
return segment; |
|||
} |
|||
|
|||
float ProjectTangent(float inPosition, float inValue, float inTangent, float projPosition) |
|||
{ |
|||
return inValue + ((projPosition - inPosition) * inTangent); |
|||
} |
|||
|
|||
float EvaluateTangent(AnimationCurve curve, float time) |
|||
{ |
|||
int prev = -1, next = 0; |
|||
for (int i = 0; i < curve.keys.Length; i++) |
|||
{ |
|||
if (time > curve.keys[i].time) |
|||
{ |
|||
prev = i; |
|||
next = i + 1; |
|||
} |
|||
else break; |
|||
} |
|||
|
|||
if (next == 0) |
|||
return 0f; |
|||
|
|||
if (prev == curve.keys.Length - 1) |
|||
return 0f; |
|||
|
|||
const float kD = 1e-3f; |
|||
float tp = Mathf.Max(time - kD, curve.keys[prev].time); |
|||
float tn = Mathf.Min(time + kD, curve.keys[next].time); |
|||
|
|||
float vp = curve.Evaluate(tp); |
|||
float vn = curve.Evaluate(tn); |
|||
|
|||
if (Mathf.Approximately(tn, tp)) |
|||
return float.PositiveInfinity; |
|||
|
|||
return (vn - vp) / (tn - tp); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 513fb6069b4efa74ea0f21f894eed362 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: a20277bec4b2cc14dbfbc6e401f1f8f1 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Rendering |
|||
{ |
|||
public static class CoreLightEditorUtilities |
|||
{ |
|||
static Vector2 SliderPlaneHandle(Vector3 origin, Vector3 axis1, Vector3 axis2, Vector2 position) |
|||
{ |
|||
Vector3 pos = origin + position.x * axis1 + position.y * axis2; |
|||
float sizeHandle = HandleUtility.GetHandleSize(pos); |
|||
bool temp = GUI.changed; |
|||
GUI.changed = false; |
|||
pos = Handles.Slider2D(pos, Vector3.forward, axis1, axis2, sizeHandle * 0.03f, Handles.DotHandleCap, 0f); |
|||
if (GUI.changed) |
|||
{ |
|||
position = new Vector2(Vector3.Dot(pos, axis1), Vector3.Dot(pos, axis2)); |
|||
} |
|||
GUI.changed |= temp; |
|||
return position; |
|||
} |
|||
|
|||
static float SliderLineHandle(Vector3 position, Vector3 direction, float value) |
|||
{ |
|||
Vector3 pos = position + direction * value; |
|||
float sizeHandle = HandleUtility.GetHandleSize(pos); |
|||
bool temp = GUI.changed; |
|||
GUI.changed = false; |
|||
pos = Handles.Slider(pos, direction, sizeHandle * 0.03f, Handles.DotHandleCap, 0f); |
|||
if (GUI.changed) |
|||
{ |
|||
value = Vector3.Dot(pos - position, direction); |
|||
} |
|||
GUI.changed |= temp; |
|||
return value; |
|||
} |
|||
|
|||
static float SliderCircleHandle(Vector3 position, Vector3 normal, Vector3 zeroValueDirection, float angleValue, float radius) |
|||
{ |
|||
zeroValueDirection.Normalize(); |
|||
normal.Normalize(); |
|||
Quaternion rot = Quaternion.AngleAxis(angleValue, normal); |
|||
Vector3 pos = position + rot * zeroValueDirection * radius; |
|||
float sizeHandle = HandleUtility.GetHandleSize(pos); |
|||
bool temp = GUI.changed; |
|||
GUI.changed = false; |
|||
Vector3 tangeant = Vector3.Cross(normal, (pos - position).normalized); |
|||
pos = Handles.Slider(pos, tangeant, sizeHandle * 0.03f, Handles.DotHandleCap, 0f); |
|||
if (GUI.changed) |
|||
{ |
|||
Vector3 dir = (pos - position).normalized; |
|||
Vector3 cross = Vector3.Cross(zeroValueDirection, dir); |
|||
int sign = ((cross - normal).sqrMagnitude < (-cross - normal).sqrMagnitude) ? 1 : -1; |
|||
angleValue = Mathf.Acos(Vector3.Dot(zeroValueDirection, dir)) * Mathf.Rad2Deg * sign; |
|||
} |
|||
GUI.changed |= temp; |
|||
return angleValue; |
|||
} |
|||
|
|||
static int s_SliderSpotAngleId; |
|||
|
|||
static float SizeSliderSpotAngle(Vector3 position, Vector3 forward, Vector3 axis, float range, float spotAngle) |
|||
{ |
|||
if (Math.Abs(spotAngle) <= 0.05f && GUIUtility.hotControl != s_SliderSpotAngleId) |
|||
return spotAngle; |
|||
var angledForward = Quaternion.AngleAxis(Mathf.Max(spotAngle, 0.05f) * 0.5f, axis) * forward; |
|||
var centerToLeftOnSphere = (angledForward * range + position) - (position + forward * range); |
|||
bool temp = GUI.changed; |
|||
GUI.changed = false; |
|||
var newMagnitude = Mathf.Max(0f, SliderLineHandle(position + forward * range, centerToLeftOnSphere.normalized, centerToLeftOnSphere.magnitude)); |
|||
if (GUI.changed) |
|||
{ |
|||
s_SliderSpotAngleId = GUIUtility.hotControl; |
|||
centerToLeftOnSphere = centerToLeftOnSphere.normalized * newMagnitude; |
|||
angledForward = (centerToLeftOnSphere + (position + forward * range) - position).normalized; |
|||
spotAngle = Mathf.Clamp(Mathf.Acos(Vector3.Dot(forward, angledForward)) * Mathf.Rad2Deg * 2, 0f, 179f); |
|||
if (spotAngle <= 0.05f || float.IsNaN(spotAngle)) |
|||
spotAngle = 0f; |
|||
} |
|||
GUI.changed |= temp; |
|||
return spotAngle; |
|||
} |
|||
|
|||
public static Color GetLightHandleColor(Color wireframeColor) |
|||
{ |
|||
Color color = wireframeColor; |
|||
color.a = Mathf.Clamp01(color.a * 2); |
|||
return (QualitySettings.activeColorSpace == ColorSpace.Linear) ? color.linear : color; |
|||
} |
|||
|
|||
public static Color GetLightBehindObjectWireframeColor(Color wireframeColor) |
|||
{ |
|||
Color color = wireframeColor; |
|||
color.a = 0.2f; |
|||
return (QualitySettings.activeColorSpace == ColorSpace.Linear) ? color.linear : color; |
|||
} |
|||
|
|||
// Don't use Handles.Disc as it break the highlight of the gizmo axis, use our own draw disc function instead for gizmo
|
|||
public static void DrawWireDisc(Quaternion q, Vector3 position, Vector3 axis, float radius) |
|||
{ |
|||
Matrix4x4 rotation = Matrix4x4.TRS(Vector3.zero, q, Vector3.one); |
|||
|
|||
Gizmos.color = Color.white; |
|||
float theta = 0.0f; |
|||
float x = radius * Mathf.Cos(theta); |
|||
float y = radius * Mathf.Sin(theta); |
|||
Vector3 pos = rotation * new Vector3(x, y, 0); |
|||
pos += position; |
|||
Vector3 newPos = pos; |
|||
Vector3 lastPos = pos; |
|||
for (theta = 0.1f; theta < 2.0f * Mathf.PI; theta += 0.1f) |
|||
{ |
|||
x = radius * Mathf.Cos(theta); |
|||
y = radius * Mathf.Sin(theta); |
|||
|
|||
newPos = rotation * new Vector3(x, y, 0); |
|||
newPos += position; |
|||
Gizmos.DrawLine(pos, newPos); |
|||
pos = newPos; |
|||
} |
|||
Gizmos.DrawLine(pos, lastPos); |
|||
} |
|||
|
|||
public static void DrawSpotlightWireframe(Vector3 outerAngleInnerAngleRange, float shadowPlaneDistance = -1f) |
|||
{ |
|||
float outerAngle = outerAngleInnerAngleRange.x; |
|||
float innerAngle = outerAngleInnerAngleRange.y; |
|||
float range = outerAngleInnerAngleRange.z; |
|||
|
|||
|
|||
var outerDiscRadius = range * Mathf.Sin(outerAngle * Mathf.Deg2Rad * 0.5f); |
|||
var outerDiscDistance = Mathf.Cos(Mathf.Deg2Rad * outerAngle * 0.5f) * range; |
|||
var vectorLineUp = Vector3.Normalize(Vector3.forward * outerDiscDistance + Vector3.up * outerDiscRadius); |
|||
var vectorLineLeft = Vector3.Normalize(Vector3.forward * outerDiscDistance + Vector3.left * outerDiscRadius); |
|||
|
|||
if(innerAngle>0f) |
|||
{ |
|||
var innerDiscRadius = range * Mathf.Sin(innerAngle * Mathf.Deg2Rad * 0.5f); |
|||
var innerDiscDistance = Mathf.Cos(Mathf.Deg2Rad * innerAngle * 0.5f) * range; |
|||
DrawConeWireframe(innerDiscRadius, innerDiscDistance); |
|||
} |
|||
DrawConeWireframe(outerDiscRadius, outerDiscDistance); |
|||
Handles.DrawWireArc(Vector3.zero, Vector3.right, vectorLineUp, outerAngle, range); |
|||
Handles.DrawWireArc(Vector3.zero, Vector3.up, vectorLineLeft, outerAngle, range); |
|||
|
|||
if (shadowPlaneDistance > 0) |
|||
{ |
|||
var shadowDiscRadius = shadowPlaneDistance * Mathf.Sin(outerAngle * Mathf.Deg2Rad * 0.5f); |
|||
var shadowDiscDistance = Mathf.Cos(Mathf.Deg2Rad * outerAngle / 2) * shadowPlaneDistance; |
|||
Handles.DrawWireDisc(Vector3.forward * shadowDiscDistance, Vector3.forward, shadowDiscRadius); |
|||
} |
|||
} |
|||
|
|||
static void DrawConeWireframe(float radius, float height) |
|||
{ |
|||
var rangeCenter = Vector3.forward * height; |
|||
var rangeUp = rangeCenter + Vector3.up * radius; |
|||
var rangeDown = rangeCenter - Vector3.up * radius; |
|||
var rangeRight = rangeCenter + Vector3.right * radius; |
|||
var rangeLeft = rangeCenter - Vector3.right * radius; |
|||
|
|||
//Draw Lines
|
|||
Handles.DrawLine(Vector3.zero, rangeUp); |
|||
Handles.DrawLine(Vector3.zero, rangeDown); |
|||
Handles.DrawLine(Vector3.zero, rangeRight); |
|||
Handles.DrawLine(Vector3.zero, rangeLeft); |
|||
|
|||
Handles.DrawWireDisc(Vector3.forward * height, Vector3.forward, radius); |
|||
} |
|||
|
|||
public static Vector3 DrawSpotlightHandle(Vector3 outerAngleInnerAngleRange) |
|||
{ |
|||
float outerAngle = outerAngleInnerAngleRange.x; |
|||
float innerAngle = outerAngleInnerAngleRange.y; |
|||
float range = outerAngleInnerAngleRange.z; |
|||
|
|||
if (innerAngle > 0f) |
|||
{ |
|||
innerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.right, range, innerAngle); |
|||
innerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.left, range, innerAngle); |
|||
innerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.up, range, innerAngle); |
|||
innerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.down, range, innerAngle); |
|||
} |
|||
|
|||
outerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.right, range, outerAngle); |
|||
outerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.left, range, outerAngle); |
|||
outerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.up, range, outerAngle); |
|||
outerAngle = SizeSliderSpotAngle(Vector3.zero, Vector3.forward, Vector3.down, range, outerAngle); |
|||
|
|||
range = SliderLineHandle(Vector3.zero, Vector3.forward, range); |
|||
|
|||
return new Vector3(outerAngle, innerAngle, range); |
|||
} |
|||
|
|||
public static void DrawAreaLightWireframe(Vector2 rectangleSize) |
|||
{ |
|||
Handles.DrawWireCube(Vector3.zero, rectangleSize); |
|||
} |
|||
|
|||
public static Vector2 DrawAreaLightHandle(Vector2 rectangleSize, bool withYAxis) |
|||
{ |
|||
float halfWidth = rectangleSize.x * 0.5f; |
|||
float halfHeight = rectangleSize.y * 0.5f; |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
halfWidth = SliderLineHandle(Vector3.zero, Vector3.right, halfWidth); |
|||
halfWidth = SliderLineHandle(Vector3.zero, Vector3.left, halfWidth); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
halfWidth = Mathf.Max(0f, halfWidth); |
|||
} |
|||
|
|||
if (withYAxis) |
|||
{ |
|||
EditorGUI.BeginChangeCheck(); |
|||
halfHeight = SliderLineHandle(Vector3.zero, Vector3.up, halfHeight); |
|||
halfHeight = SliderLineHandle(Vector3.zero, Vector3.down, halfHeight); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
halfHeight = Mathf.Max(0f, halfHeight); |
|||
} |
|||
} |
|||
|
|||
return new Vector2(halfWidth * 2f, halfHeight * 2f); |
|||
} |
|||
|
|||
// Same as Gizmo.DrawFrustum except that when aspect is below one, fov represent fovX instead of fovY
|
|||
// Use to match our light frustum pyramid behavior
|
|||
public static void DrawPyramidFrustumWireframe(Vector4 aspectFovMaxRangeMinRange, float distanceTruncPlane = 0f) |
|||
{ |
|||
float aspect = aspectFovMaxRangeMinRange.x; |
|||
float fov = aspectFovMaxRangeMinRange.y; |
|||
float maxRange = aspectFovMaxRangeMinRange.z; |
|||
float minRange = aspectFovMaxRangeMinRange.w; |
|||
float tanfov = Mathf.Tan(Mathf.Deg2Rad * fov * 0.5f); |
|||
|
|||
var startAngles = new Vector3[4]; |
|||
if (minRange > 0.0f) |
|||
{ |
|||
startAngles = GetFrustrumProjectedRectAngles(minRange, aspect, tanfov); |
|||
Handles.DrawLine(startAngles[0], startAngles[1]); |
|||
Handles.DrawLine(startAngles[1], startAngles[2]); |
|||
Handles.DrawLine(startAngles[2], startAngles[3]); |
|||
Handles.DrawLine(startAngles[3], startAngles[0]); |
|||
} |
|||
|
|||
if (distanceTruncPlane > 0f) |
|||
{ |
|||
var truncAngles = GetFrustrumProjectedRectAngles(distanceTruncPlane, aspect, tanfov); |
|||
Handles.DrawLine(truncAngles[0], truncAngles[1]); |
|||
Handles.DrawLine(truncAngles[1], truncAngles[2]); |
|||
Handles.DrawLine(truncAngles[2], truncAngles[3]); |
|||
Handles.DrawLine(truncAngles[3], truncAngles[0]); |
|||
} |
|||
|
|||
var endAngles = GetFrustrumProjectedRectAngles(maxRange, aspect, tanfov); |
|||
Handles.DrawLine(endAngles[0], endAngles[1]); |
|||
Handles.DrawLine(endAngles[1], endAngles[2]); |
|||
Handles.DrawLine(endAngles[2], endAngles[3]); |
|||
Handles.DrawLine(endAngles[3], endAngles[0]); |
|||
|
|||
Handles.DrawLine(startAngles[0], endAngles[0]); |
|||
Handles.DrawLine(startAngles[1], endAngles[1]); |
|||
Handles.DrawLine(startAngles[2], endAngles[2]); |
|||
Handles.DrawLine(startAngles[3], endAngles[3]); |
|||
} |
|||
|
|||
// Same as Gizmo.DrawFrustum except that when aspect is below one, fov represent fovX instead of fovY
|
|||
// Use to match our light frustum pyramid behavior
|
|||
public static void DrawSpherePortionWireframe(Vector4 aspectFovMaxRangeMinRange, float distanceTruncPlane = 0f) |
|||
{ |
|||
float aspect = aspectFovMaxRangeMinRange.x; |
|||
float fov = aspectFovMaxRangeMinRange.y; |
|||
float maxRange = aspectFovMaxRangeMinRange.z; |
|||
float minRange = aspectFovMaxRangeMinRange.w; |
|||
float tanfov = Mathf.Tan(Mathf.Deg2Rad * fov * 0.5f); |
|||
|
|||
var startAngles = new Vector3[4]; |
|||
if (minRange > 0f) |
|||
{ |
|||
startAngles = GetFrustrumProjectedRectAngles(minRange, aspect, tanfov); |
|||
Handles.DrawLine(startAngles[0], startAngles[1]); |
|||
Handles.DrawLine(startAngles[1], startAngles[2]); |
|||
Handles.DrawLine(startAngles[2], startAngles[3]); |
|||
Handles.DrawLine(startAngles[3], startAngles[0]); |
|||
} |
|||
|
|||
if (distanceTruncPlane > 0f) |
|||
{ |
|||
var truncAngles = GetFrustrumProjectedRectAngles(distanceTruncPlane, aspect, tanfov); |
|||
Handles.DrawLine(truncAngles[0], truncAngles[1]); |
|||
Handles.DrawLine(truncAngles[1], truncAngles[2]); |
|||
Handles.DrawLine(truncAngles[2], truncAngles[3]); |
|||
Handles.DrawLine(truncAngles[3], truncAngles[0]); |
|||
} |
|||
|
|||
var endAngles = GetSphericalProjectedRectAngles(maxRange, aspect, tanfov); |
|||
var planProjectedCrossNormal0 = new Vector3(endAngles[0].y, -endAngles[0].x, 0).normalized; |
|||
var planProjectedCrossNormal1 = new Vector3(endAngles[1].y, -endAngles[1].x, 0).normalized; |
|||
Vector3[] faceNormals = new[] { |
|||
Vector3.right - Vector3.Dot((endAngles[3] + endAngles[0]).normalized, Vector3.right) * (endAngles[3] + endAngles[0]).normalized, |
|||
Vector3.up - Vector3.Dot((endAngles[0] + endAngles[1]).normalized, Vector3.up) * (endAngles[0] + endAngles[1]).normalized, |
|||
Vector3.left - Vector3.Dot((endAngles[1] + endAngles[2]).normalized, Vector3.left) * (endAngles[1] + endAngles[2]).normalized, |
|||
Vector3.down - Vector3.Dot((endAngles[2] + endAngles[3]).normalized, Vector3.down) * (endAngles[2] + endAngles[3]).normalized, |
|||
//cross
|
|||
planProjectedCrossNormal0 - Vector3.Dot((endAngles[1] + endAngles[3]).normalized, planProjectedCrossNormal0) * (endAngles[1] + endAngles[3]).normalized, |
|||
planProjectedCrossNormal1 - Vector3.Dot((endAngles[0] + endAngles[2]).normalized, planProjectedCrossNormal1) * (endAngles[0] + endAngles[2]).normalized, |
|||
}; |
|||
|
|||
float[] faceAngles = new[] { |
|||
Vector3.Angle(endAngles[3], endAngles[0]), |
|||
Vector3.Angle(endAngles[0], endAngles[1]), |
|||
Vector3.Angle(endAngles[1], endAngles[2]), |
|||
Vector3.Angle(endAngles[2], endAngles[3]), |
|||
Vector3.Angle(endAngles[1], endAngles[3]), |
|||
Vector3.Angle(endAngles[0], endAngles[2]), |
|||
}; |
|||
|
|||
Handles.DrawWireArc(Vector3.zero, faceNormals[0], endAngles[0], faceAngles[0], maxRange); |
|||
Handles.DrawWireArc(Vector3.zero, faceNormals[1], endAngles[1], faceAngles[1], maxRange); |
|||
Handles.DrawWireArc(Vector3.zero, faceNormals[2], endAngles[2], faceAngles[2], maxRange); |
|||
Handles.DrawWireArc(Vector3.zero, faceNormals[3], endAngles[3], faceAngles[3], maxRange); |
|||
Handles.DrawWireArc(Vector3.zero, faceNormals[4], endAngles[0], faceAngles[4], maxRange); |
|||
Handles.DrawWireArc(Vector3.zero, faceNormals[5], endAngles[1], faceAngles[5], maxRange); |
|||
|
|||
Handles.DrawLine(startAngles[0], endAngles[0]); |
|||
Handles.DrawLine(startAngles[1], endAngles[1]); |
|||
Handles.DrawLine(startAngles[2], endAngles[2]); |
|||
Handles.DrawLine(startAngles[3], endAngles[3]); |
|||
} |
|||
|
|||
static Vector3[] GetFrustrumProjectedRectAngles(float distance, float aspect, float tanFOV) |
|||
{ |
|||
Vector3 sizeX; |
|||
Vector3 sizeY; |
|||
float minXYTruncSize = distance * tanFOV; |
|||
if (aspect >= 1.0f) |
|||
{ |
|||
sizeX = new Vector3(minXYTruncSize * aspect, 0, 0); |
|||
sizeY = new Vector3(0, minXYTruncSize, 0); |
|||
} |
|||
else |
|||
{ |
|||
sizeX = new Vector3(minXYTruncSize, 0, 0); |
|||
sizeY = new Vector3(0, minXYTruncSize / aspect, 0); |
|||
} |
|||
Vector3 center = new Vector3(0, 0, distance); |
|||
Vector3[] angles = |
|||
{ |
|||
center + sizeX + sizeY, |
|||
center - sizeX + sizeY, |
|||
center - sizeX - sizeY, |
|||
center + sizeX - sizeY |
|||
}; |
|||
|
|||
return angles; |
|||
} |
|||
|
|||
static Vector3[] GetSphericalProjectedRectAngles(float distance, float aspect, float tanFOV) |
|||
{ |
|||
var angles = GetFrustrumProjectedRectAngles(distance, aspect, tanFOV); |
|||
for (int index = 0; index < 4; ++index) |
|||
angles[index] = angles[index].normalized * distance; |
|||
return angles; |
|||
} |
|||
|
|||
public static Vector4 DrawPyramidFrustumHandle(Vector4 aspectFovMaxRangeMinRange, bool useNearPlane, float minAspect = 0.05f, float maxAspect = 20f, float minFov = 1f) |
|||
{ |
|||
float aspect = aspectFovMaxRangeMinRange.x; |
|||
float fov = aspectFovMaxRangeMinRange.y; |
|||
float maxRange = aspectFovMaxRangeMinRange.z; |
|||
float minRange = aspectFovMaxRangeMinRange.w; |
|||
float tanfov = Mathf.Tan(Mathf.Deg2Rad * fov * 0.5f); |
|||
|
|||
var e = GetFrustrumProjectedRectAngles(maxRange, aspect, tanfov); |
|||
|
|||
if (useNearPlane) |
|||
{ |
|||
minRange = SliderLineHandle(Vector3.zero, Vector3.forward, minRange); |
|||
} |
|||
|
|||
maxRange = SliderLineHandle(Vector3.zero, Vector3.forward, maxRange); |
|||
|
|||
float distanceRight = HandleUtility.DistanceToLine(e[0], e[3]); |
|||
float distanceLeft = HandleUtility.DistanceToLine(e[1], e[2]); |
|||
float distanceUp = HandleUtility.DistanceToLine(e[0], e[1]); |
|||
float distanceDown = HandleUtility.DistanceToLine(e[2], e[3]); |
|||
|
|||
int pointIndex = 0; |
|||
if (distanceRight < distanceLeft) |
|||
{ |
|||
if (distanceUp < distanceDown) |
|||
pointIndex = 0; |
|||
else |
|||
pointIndex = 3; |
|||
} |
|||
else |
|||
{ |
|||
if (distanceUp < distanceDown) |
|||
pointIndex = 1; |
|||
else |
|||
pointIndex = 2; |
|||
} |
|||
|
|||
Vector2 send = e[pointIndex]; |
|||
Vector3 farEnd = new Vector3(0, 0, maxRange); |
|||
EditorGUI.BeginChangeCheck(); |
|||
Vector2 received = SliderPlaneHandle(farEnd, Vector3.right, Vector3.up, send); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
bool fixedFov = Event.current.control && !Event.current.shift; |
|||
bool fixedAspect = Event.current.shift && !Event.current.control; |
|||
|
|||
//work on positive quadrant
|
|||
int xSign = send.x < 0f ? -1 : 1; |
|||
int ySign = send.y < 0f ? -1 : 1; |
|||
Vector2 corrected = new Vector2(received.x * xSign, received.y * ySign); |
|||
|
|||
//fixed aspect correction
|
|||
if (fixedAspect) |
|||
{ |
|||
corrected.x = corrected.y * aspect; |
|||
} |
|||
|
|||
//remove aspect deadzone
|
|||
if (corrected.x > maxAspect * corrected.y) |
|||
{ |
|||
corrected.y = corrected.x * minAspect; |
|||
} |
|||
if (corrected.x < minAspect * corrected.y) |
|||
{ |
|||
corrected.x = corrected.y / maxAspect; |
|||
} |
|||
|
|||
//remove fov deadzone
|
|||
float deadThresholdFoV = Mathf.Tan(Mathf.Deg2Rad * minFov * 0.5f) * maxRange; |
|||
corrected.x = Mathf.Max(corrected.x, deadThresholdFoV); |
|||
corrected.y = Mathf.Max(corrected.y, deadThresholdFoV, Mathf.Epsilon * 100); //prevent any division by zero
|
|||
|
|||
if (!fixedAspect) |
|||
{ |
|||
aspect = corrected.x / corrected.y; |
|||
} |
|||
float min = Mathf.Min(corrected.x, corrected.y); |
|||
if (!fixedFov && maxRange > Mathf.Epsilon * 100) |
|||
{ |
|||
fov = Mathf.Atan(min / maxRange) * 2f * Mathf.Rad2Deg; |
|||
} |
|||
} |
|||
|
|||
return new Vector4(aspect, fov, maxRange, minRange); |
|||
} |
|||
|
|||
public static Vector4 DrawSpherePortionHandle(Vector4 aspectFovMaxRangeMinRange, bool useNearPlane, float minAspect = 0.05f, float maxAspect = 20f, float minFov = 1f) |
|||
{ |
|||
float aspect = aspectFovMaxRangeMinRange.x; |
|||
float fov = aspectFovMaxRangeMinRange.y; |
|||
float maxRange = aspectFovMaxRangeMinRange.z; |
|||
float minRange = aspectFovMaxRangeMinRange.w; |
|||
float tanfov = Mathf.Tan(Mathf.Deg2Rad * fov * 0.5f); |
|||
|
|||
var endAngles = GetSphericalProjectedRectAngles(maxRange, aspect, tanfov); |
|||
|
|||
if (useNearPlane) |
|||
{ |
|||
minRange = SliderLineHandle(Vector3.zero, Vector3.forward, minRange); |
|||
} |
|||
|
|||
maxRange = SliderLineHandle(Vector3.zero, Vector3.forward, maxRange); |
|||
|
|||
float distanceRight = HandleUtility.DistanceToLine(endAngles[0], endAngles[3]); |
|||
float distanceLeft = HandleUtility.DistanceToLine(endAngles[1], endAngles[2]); |
|||
float distanceUp = HandleUtility.DistanceToLine(endAngles[0], endAngles[1]); |
|||
float distanceDown = HandleUtility.DistanceToLine(endAngles[2], endAngles[3]); |
|||
|
|||
int pointIndex = 0; |
|||
if (distanceRight < distanceLeft) |
|||
{ |
|||
if (distanceUp < distanceDown) |
|||
pointIndex = 0; |
|||
else |
|||
pointIndex = 3; |
|||
} |
|||
else |
|||
{ |
|||
if (distanceUp < distanceDown) |
|||
pointIndex = 1; |
|||
else |
|||
pointIndex = 2; |
|||
} |
|||
|
|||
Vector2 send = endAngles[pointIndex]; |
|||
Vector3 farEnd = new Vector3(0, 0, endAngles[0].z); |
|||
EditorGUI.BeginChangeCheck(); |
|||
Vector2 received = SliderPlaneHandle(farEnd, Vector3.right, Vector3.up, send); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
bool fixedFov = Event.current.control && !Event.current.shift; |
|||
bool fixedAspect = Event.current.shift && !Event.current.control; |
|||
|
|||
//work on positive quadrant
|
|||
int xSign = send.x < 0f ? -1 : 1; |
|||
int ySign = send.y < 0f ? -1 : 1; |
|||
Vector2 corrected = new Vector2(received.x * xSign, received.y * ySign); |
|||
|
|||
//fixed aspect correction
|
|||
if (fixedAspect) |
|||
{ |
|||
corrected.x = corrected.y * aspect; |
|||
} |
|||
|
|||
//remove aspect deadzone
|
|||
if (corrected.x > maxAspect * corrected.y) |
|||
{ |
|||
corrected.y = corrected.x * minAspect; |
|||
} |
|||
if (corrected.x < minAspect * corrected.y) |
|||
{ |
|||
corrected.x = corrected.y / maxAspect; |
|||
} |
|||
|
|||
//remove fov deadzone
|
|||
float deadThresholdFoV = Mathf.Tan(Mathf.Deg2Rad * minFov * 0.5f) * maxRange; |
|||
corrected.x = Mathf.Max(corrected.x, deadThresholdFoV); |
|||
corrected.y = Mathf.Max(corrected.y, deadThresholdFoV, Mathf.Epsilon * 100); //prevent any division by zero
|
|||
|
|||
if (!fixedAspect) |
|||
{ |
|||
aspect = corrected.x / corrected.y; |
|||
} |
|||
float min = Mathf.Min(corrected.x, corrected.y); |
|||
if (!fixedFov && maxRange > Mathf.Epsilon * 100) |
|||
{ |
|||
fov = Mathf.Atan(min / maxRange) * 2f * Mathf.Rad2Deg; |
|||
} |
|||
} |
|||
|
|||
return new Vector4(aspect, fov, maxRange, minRange); |
|||
} |
|||
|
|||
public static void DrawOrthoFrustumWireframe(Vector4 widthHeightMaxRangeMinRange, float distanceTruncPlane = 0f) |
|||
{ |
|||
float halfWidth = widthHeightMaxRangeMinRange.x * 0.5f; |
|||
float halfHeight = widthHeightMaxRangeMinRange.y * 0.5f; |
|||
float maxRange = widthHeightMaxRangeMinRange.z; |
|||
float minRange = widthHeightMaxRangeMinRange.w; |
|||
|
|||
Vector3 sizeX = new Vector3(halfWidth, 0, 0); |
|||
Vector3 sizeY = new Vector3(0, halfHeight, 0); |
|||
Vector3 nearEnd = new Vector3(0, 0, minRange); |
|||
Vector3 farEnd = new Vector3(0, 0, maxRange); |
|||
|
|||
Vector3 s1 = nearEnd + sizeX + sizeY; |
|||
Vector3 s2 = nearEnd - sizeX + sizeY; |
|||
Vector3 s3 = nearEnd - sizeX - sizeY; |
|||
Vector3 s4 = nearEnd + sizeX - sizeY; |
|||
|
|||
Vector3 e1 = farEnd + sizeX + sizeY; |
|||
Vector3 e2 = farEnd - sizeX + sizeY; |
|||
Vector3 e3 = farEnd - sizeX - sizeY; |
|||
Vector3 e4 = farEnd + sizeX - sizeY; |
|||
|
|||
Handles.DrawLine(s1, s2); |
|||
Handles.DrawLine(s2, s3); |
|||
Handles.DrawLine(s3, s4); |
|||
Handles.DrawLine(s4, s1); |
|||
|
|||
Handles.DrawLine(e1, e2); |
|||
Handles.DrawLine(e2, e3); |
|||
Handles.DrawLine(e3, e4); |
|||
Handles.DrawLine(e4, e1); |
|||
|
|||
Handles.DrawLine(s1, e1); |
|||
Handles.DrawLine(s2, e2); |
|||
Handles.DrawLine(s3, e3); |
|||
Handles.DrawLine(s4, e4); |
|||
|
|||
if (distanceTruncPlane> 0f) |
|||
{ |
|||
Vector3 truncPoint = new Vector3(0, 0, distanceTruncPlane); |
|||
Vector3 t1 = truncPoint + sizeX + sizeY; |
|||
Vector3 t2 = truncPoint - sizeX + sizeY; |
|||
Vector3 t3 = truncPoint - sizeX - sizeY; |
|||
Vector3 t4 = truncPoint + sizeX - sizeY; |
|||
|
|||
Handles.DrawLine(t1, t2); |
|||
Handles.DrawLine(t2, t3); |
|||
Handles.DrawLine(t3, t4); |
|||
Handles.DrawLine(t4, t1); |
|||
} |
|||
} |
|||
public static Vector4 DrawOrthoFrustumHandle(Vector4 widthHeightMaxRangeMinRange, bool useNearHandle) |
|||
{ |
|||
float halfWidth = widthHeightMaxRangeMinRange.x * 0.5f; |
|||
float halfHeight = widthHeightMaxRangeMinRange.y * 0.5f; |
|||
float maxRange = widthHeightMaxRangeMinRange.z; |
|||
float minRange = widthHeightMaxRangeMinRange.w; |
|||
Vector3 farEnd = new Vector3(0, 0, maxRange); |
|||
|
|||
if (useNearHandle) |
|||
{ |
|||
minRange = SliderLineHandle(Vector3.zero, Vector3.forward, minRange); |
|||
} |
|||
|
|||
maxRange = SliderLineHandle(Vector3.zero, Vector3.forward, maxRange); |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
halfWidth = SliderLineHandle(farEnd, Vector3.right, halfWidth); |
|||
halfWidth = SliderLineHandle(farEnd, Vector3.left, halfWidth); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
halfWidth = Mathf.Max(0f, halfWidth); |
|||
} |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
halfHeight = SliderLineHandle(farEnd, Vector3.up, halfHeight); |
|||
halfHeight = SliderLineHandle(farEnd, Vector3.down, halfHeight); |
|||
if (EditorGUI.EndChangeCheck()) |
|||
{ |
|||
halfHeight = Mathf.Max(0f, halfHeight); |
|||
} |
|||
|
|||
return new Vector4(halfWidth * 2f, halfHeight * 2f, maxRange, minRange); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 8f89186f308429942b61ec29b405e2dd |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 2d4bb8a5c15072e4baaabe7e161d42b6 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEngine; |
|||
using UnityEditor.ShortcutManagement; |
|||
using UnityEngine.UIElements; |
|||
|
|||
namespace UnityEditor.Rendering.LookDev |
|||
{ |
|||
class CameraController : Manipulator |
|||
{ |
|||
float m_StartZoom = 0.0f; |
|||
float m_ZoomSpeed = 0.0f; |
|||
|
|||
float m_TotalMotion = 0.0f; |
|||
Vector3 m_MotionDirection = new Vector3(); |
|||
|
|||
float m_FlySpeedNormalized = .5f; |
|||
float m_FlySpeed = 1f; |
|||
float m_FlySpeedAccelerated = 0f; |
|||
const float m_FlySpeedMin = .01f; |
|||
const float m_FlySpeedMax = 2f; |
|||
//[TODO: check if necessary to add hability to deactivate acceleration]
|
|||
const float k_FlyAcceleration = 1.1f; |
|||
bool m_ShiftBoostedFly = false; |
|||
bool m_InFlyMotion; |
|||
|
|||
bool m_IsDragging; |
|||
ViewTool m_BehaviorState; |
|||
static TimeHelper s_Timer = new TimeHelper(); |
|||
|
|||
protected CameraState m_CameraState; |
|||
DisplayWindow m_Window; |
|||
protected Action m_Focused; |
|||
|
|||
Rect screen => target.contentRect; |
|||
|
|||
bool inFlyMotion |
|||
{ |
|||
get => m_InFlyMotion; |
|||
set |
|||
{ |
|||
if (value ^ m_InFlyMotion) |
|||
{ |
|||
if (value) |
|||
{ |
|||
s_Timer.Begin(); |
|||
EditorApplication.update += UpdateMotion; |
|||
} |
|||
else |
|||
{ |
|||
m_FlySpeedAccelerated = 0f; |
|||
m_MotionDirection = Vector3.zero; |
|||
m_ShiftBoostedFly = false; |
|||
EditorApplication.update -= UpdateMotion; |
|||
} |
|||
m_InFlyMotion = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
float flySpeedNormalized |
|||
{ |
|||
get => m_FlySpeedNormalized; |
|||
set |
|||
{ |
|||
m_FlySpeedNormalized = Mathf.Clamp01(value); |
|||
float speed = Mathf.Lerp(m_FlySpeedMin, m_FlySpeedMax, m_FlySpeedNormalized); |
|||
// Round to nearest decimal: 2 decimal points when between [0.01, 0.1]; 1 decimal point when between [0.1, 10]; integral between [10, 99]
|
|||
speed = (float)(System.Math.Round((double)speed, speed < 0.1f ? 2 : speed < 10f ? 1 : 0)); |
|||
m_FlySpeed = Mathf.Clamp(speed, m_FlySpeedMin, m_FlySpeedMax); |
|||
} |
|||
} |
|||
float flySpeed |
|||
{ |
|||
get => m_FlySpeed; |
|||
set => flySpeedNormalized = Mathf.InverseLerp(m_FlySpeedMin, m_FlySpeedMax, value); |
|||
} |
|||
|
|||
virtual protected bool isDragging |
|||
{ |
|||
get => m_IsDragging; |
|||
set |
|||
{ |
|||
//As in scene view, stop dragging as first button is release in case of multiple button down
|
|||
if (value ^ m_IsDragging) |
|||
{ |
|||
if (value) |
|||
{ |
|||
target.RegisterCallback<MouseMoveEvent>(OnMouseDrag); |
|||
target.CaptureMouse(); |
|||
EditorGUIUtility.SetWantsMouseJumping(1); |
|||
} |
|||
else |
|||
{ |
|||
EditorGUIUtility.SetWantsMouseJumping(0); |
|||
target.ReleaseMouse(); |
|||
target.UnregisterCallback<MouseMoveEvent>(OnMouseDrag); |
|||
} |
|||
m_IsDragging = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public CameraController(CameraState cameraState, DisplayWindow window, Action focused) |
|||
{ |
|||
m_CameraState = cameraState; |
|||
m_Window = window; |
|||
m_Focused = focused; |
|||
} |
|||
|
|||
private void ResetCameraControl() |
|||
{ |
|||
isDragging = false; |
|||
inFlyMotion = false; |
|||
m_BehaviorState = ViewTool.None; |
|||
} |
|||
|
|||
protected virtual void OnScrollWheel(WheelEvent evt) |
|||
{ |
|||
// See UnityEditor.SceneViewMotion.HandleScrollWheel
|
|||
switch (m_BehaviorState) |
|||
{ |
|||
case ViewTool.FPS: OnChangeFPSCameraSpeed(evt); break; |
|||
default: OnZoom(evt); break; |
|||
} |
|||
} |
|||
|
|||
void OnMouseDrag(MouseMoveEvent evt) |
|||
{ |
|||
switch (m_BehaviorState) |
|||
{ |
|||
case ViewTool.Orbit: OnMouseDragOrbit(evt); break; |
|||
case ViewTool.FPS: OnMouseDragFPS(evt); break; |
|||
case ViewTool.Pan: OnMouseDragPan(evt); break; |
|||
case ViewTool.Zoom: OnMouseDragZoom(evt); break; |
|||
default: break; |
|||
} |
|||
} |
|||
|
|||
void OnKeyDown(KeyDownEvent evt) |
|||
{ |
|||
OnKeyDownFPS(evt); |
|||
OnKeyDownReset(evt); |
|||
} |
|||
|
|||
void OnChangeFPSCameraSpeed(WheelEvent evt) |
|||
{ |
|||
float scrollWheelDelta = evt.delta.y; |
|||
flySpeedNormalized -= scrollWheelDelta * .01f; |
|||
string cameraSpeedDisplayValue = flySpeed.ToString(flySpeed < 0.1f ? "F2" : flySpeed < 10f ? "F1" : "F0"); |
|||
if (flySpeed < 0.1f) |
|||
cameraSpeedDisplayValue = cameraSpeedDisplayValue.TrimStart(new Char[] { '0' }); |
|||
GUIContent cameraSpeedContent = EditorGUIUtility.TrTempContent( |
|||
$"{cameraSpeedDisplayValue}x"); |
|||
m_Window.ShowNotification(cameraSpeedContent, .5f); |
|||
evt.StopPropagation(); |
|||
} |
|||
|
|||
void OnZoom(WheelEvent evt) |
|||
{ |
|||
float scrollWheelDelta = evt.delta.y; |
|||
float relativeDelta = m_CameraState.viewSize * scrollWheelDelta * .015f; |
|||
const float deltaCutoff = .3f; |
|||
if (relativeDelta > 0 && relativeDelta < deltaCutoff) |
|||
relativeDelta = deltaCutoff; |
|||
else if (relativeDelta < 0 && relativeDelta > -deltaCutoff) |
|||
relativeDelta = -deltaCutoff; |
|||
m_CameraState.viewSize += relativeDelta; |
|||
evt.StopPropagation(); |
|||
} |
|||
|
|||
void OnMouseDragOrbit(MouseMoveEvent evt) |
|||
{ |
|||
Quaternion rotation = m_CameraState.rotation; |
|||
rotation = Quaternion.AngleAxis(evt.mouseDelta.y * .003f * Mathf.Rad2Deg, rotation * Vector3.right) * rotation; |
|||
rotation = Quaternion.AngleAxis(evt.mouseDelta.x * .003f * Mathf.Rad2Deg, Vector3.up) * rotation; |
|||
m_CameraState.rotation = rotation; |
|||
evt.StopPropagation(); |
|||
} |
|||
|
|||
void OnMouseDragFPS(MouseMoveEvent evt) |
|||
{ |
|||
Vector3 camPos = m_CameraState.pivot - m_CameraState.rotation * Vector3.forward * m_CameraState.distanceFromPivot; |
|||
Quaternion rotation = m_CameraState.rotation; |
|||
rotation = Quaternion.AngleAxis(evt.mouseDelta.y * .003f * Mathf.Rad2Deg, rotation * Vector3.right) * rotation; |
|||
rotation = Quaternion.AngleAxis(evt.mouseDelta.x * .003f * Mathf.Rad2Deg, Vector3.up) * rotation; |
|||
m_CameraState.rotation = rotation; |
|||
m_CameraState.pivot = camPos + rotation * Vector3.forward * m_CameraState.distanceFromPivot; |
|||
evt.StopPropagation(); |
|||
} |
|||
|
|||
void OnMouseDragPan(MouseMoveEvent evt) |
|||
{ |
|||
//[TODO: fix WorldToScreenPoint and ScreenToWorldPoint
|
|||
var screenPos = m_CameraState.QuickProjectPivotInScreen(screen); |
|||
screenPos += new Vector3(evt.mouseDelta.x, -evt.mouseDelta.y, 0); |
|||
//Vector3 newWorldPos = m_CameraState.ScreenToWorldPoint(screen, screenPos);
|
|||
Vector3 newWorldPos = m_CameraState.QuickReprojectionWithFixedFOVOnPivotPlane(screen, screenPos); |
|||
Vector3 worldDelta = newWorldPos - m_CameraState.pivot; |
|||
worldDelta *= EditorGUIUtility.pixelsPerPoint; |
|||
if (evt.shiftKey) |
|||
worldDelta *= 4; |
|||
m_CameraState.pivot += worldDelta; |
|||
evt.StopPropagation(); |
|||
} |
|||
|
|||
void OnMouseDragZoom(MouseMoveEvent evt) |
|||
{ |
|||
float zoomDelta = HandleUtility.niceMouseDeltaZoom * (evt.shiftKey ? 9 : 3); |
|||
m_TotalMotion += zoomDelta; |
|||
if (m_TotalMotion < 0) |
|||
m_CameraState.viewSize = m_StartZoom * (1 + m_TotalMotion * .001f); |
|||
else |
|||
m_CameraState.viewSize = m_CameraState.viewSize + zoomDelta * m_ZoomSpeed * .003f; |
|||
evt.StopPropagation(); |
|||
} |
|||
|
|||
void OnKeyDownReset(KeyDownEvent evt) |
|||
{ |
|||
if (evt.keyCode == KeyCode.Escape) |
|||
ResetCameraControl(); |
|||
evt.StopPropagation(); |
|||
} |
|||
|
|||
void OnKeyDownFPS(KeyDownEvent evt) |
|||
{ |
|||
if (m_BehaviorState != ViewTool.FPS) |
|||
return; |
|||
|
|||
//Note: Keydown is called in loop but between first occurence of the
|
|||
// loop and laters, there is a small pause. To deal with this, we
|
|||
// need to register the UpdateMovement function to the Editor update
|
|||
KeyCombination combination; |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Forward", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.forward, evt); |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Backward", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.back, evt); |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Left", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.left, evt); |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Right", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.right, evt); |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Up", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.up, evt); |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Down", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.down, evt); |
|||
} |
|||
|
|||
void OnKeyUpFPS(KeyUpEvent evt) |
|||
{ |
|||
if (m_BehaviorState != ViewTool.FPS) |
|||
return; |
|||
|
|||
KeyCombination combination; |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Forward", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.back, evt); |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Backward", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.forward, evt); |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Left", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.right, evt); |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Right", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.left, evt); |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Up", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.down, evt); |
|||
if (GetKeyCombinationByID("3D Viewport/Fly Mode Down", out combination) && combination.Match(evt)) |
|||
RegisterMotionChange(Vector3.up, evt); |
|||
} |
|||
|
|||
void RegisterMotionChange<T>(Vector3 direction, KeyboardEventBase<T> evt) |
|||
where T : KeyboardEventBase<T>, new() |
|||
{ |
|||
m_ShiftBoostedFly = evt.shiftKey; |
|||
m_MotionDirection = new Vector3( |
|||
Mathf.Clamp(m_MotionDirection.x + direction.x, -1, 1), |
|||
Mathf.Clamp(m_MotionDirection.y + direction.y, -1, 1), |
|||
Mathf.Clamp(m_MotionDirection.z + direction.z, -1, 1)); |
|||
inFlyMotion = m_MotionDirection.sqrMagnitude != 0f; |
|||
evt.StopPropagation(); |
|||
} |
|||
|
|||
Vector3 GetMotionDirection() |
|||
{ |
|||
var deltaTime = s_Timer.Update(); |
|||
Vector3 result; |
|||
float speed = (m_ShiftBoostedFly ? 5 * flySpeed : flySpeed); |
|||
if (m_FlySpeedAccelerated == 0) |
|||
m_FlySpeedAccelerated = 9; |
|||
else |
|||
m_FlySpeedAccelerated *= Mathf.Pow(k_FlyAcceleration, deltaTime); |
|||
result = m_MotionDirection.normalized * m_FlySpeedAccelerated * speed * deltaTime; |
|||
return result; |
|||
} |
|||
|
|||
void UpdateMotion() |
|||
{ |
|||
if (Mathf.Approximately(m_MotionDirection.sqrMagnitude, 0f)) |
|||
inFlyMotion = false; |
|||
else |
|||
m_CameraState.pivot += m_CameraState.rotation * GetMotionDirection(); |
|||
} |
|||
|
|||
bool GetKeyCombinationByID(string ID, out KeyCombination combination) |
|||
{ |
|||
var sequence = ShortcutManager.instance.GetShortcutBinding(ID).keyCombinationSequence.GetEnumerator(); |
|||
if (sequence.MoveNext()) //have a first entry
|
|||
{ |
|||
combination = new KeyCombination(sequence.Current); |
|||
return true; |
|||
} |
|||
else |
|||
{ |
|||
combination = default; |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
|
|||
void OnMouseUp(MouseUpEvent evt) |
|||
{ |
|||
ResetCameraControl(); |
|||
evt.StopPropagation(); |
|||
} |
|||
|
|||
void OnMouseDown(MouseDownEvent evt) |
|||
{ |
|||
bool onMac = Application.platform == RuntimePlatform.OSXEditor; |
|||
|
|||
if (evt.button == 2) |
|||
m_BehaviorState = ViewTool.Pan; |
|||
else if (evt.button == 0 && evt.ctrlKey && onMac || evt.button == 1 && evt.altKey) |
|||
{ |
|||
m_BehaviorState = ViewTool.Zoom; |
|||
m_StartZoom = m_CameraState.viewSize; |
|||
m_ZoomSpeed = Mathf.Max(Mathf.Abs(m_StartZoom), .3f); |
|||
m_TotalMotion = 0; |
|||
} |
|||
else if (evt.button == 0) |
|||
m_BehaviorState = ViewTool.Orbit; |
|||
else if (evt.button == 1 && !evt.altKey) |
|||
m_BehaviorState = ViewTool.FPS; |
|||
|
|||
// see also SceneView.HandleClickAndDragToFocus()
|
|||
if (evt.button == 1 && onMac) |
|||
m_Window.Focus(); |
|||
|
|||
target.Focus(); //required for keyboard event
|
|||
isDragging = true; |
|||
evt.StopPropagation(); |
|||
|
|||
m_Focused?.Invoke(); |
|||
} |
|||
|
|||
protected override void RegisterCallbacksOnTarget() |
|||
{ |
|||
target.focusable = true; //prerequisite for being focusable and recerive keydown events
|
|||
target.RegisterCallback<MouseUpEvent>(OnMouseUp); |
|||
target.RegisterCallback<MouseDownEvent>(OnMouseDown); |
|||
target.RegisterCallback<WheelEvent>(OnScrollWheel); |
|||
target.RegisterCallback<KeyDownEvent>(OnKeyDown); |
|||
target.RegisterCallback<KeyUpEvent>(OnKeyUpFPS); |
|||
} |
|||
|
|||
protected override void UnregisterCallbacksFromTarget() |
|||
{ |
|||
target.UnregisterCallback<MouseUpEvent>(OnMouseUp); |
|||
target.UnregisterCallback<MouseDownEvent>(OnMouseDown); |
|||
target.UnregisterCallback<WheelEvent>(OnScrollWheel); |
|||
target.UnregisterCallback<KeyDownEvent>(OnKeyDown); |
|||
target.UnregisterCallback<KeyUpEvent>(OnKeyUpFPS); |
|||
} |
|||
|
|||
struct KeyCombination |
|||
{ |
|||
KeyCode key; |
|||
EventModifiers modifier; |
|||
public bool shiftOnLastMatch; |
|||
|
|||
public KeyCombination(UnityEditor.ShortcutManagement.KeyCombination shortcutCombination) |
|||
{ |
|||
key = shortcutCombination.keyCode; |
|||
modifier = EventModifiers.None; |
|||
if ((shortcutCombination.modifiers & ShortcutModifiers.Shift) != 0) |
|||
modifier |= EventModifiers.Shift; |
|||
if ((shortcutCombination.modifiers & ShortcutModifiers.Alt) != 0) |
|||
modifier |= EventModifiers.Alt; |
|||
if ((shortcutCombination.modifiers & ShortcutModifiers.Action) != 0) |
|||
{ |
|||
if (Application.platform == RuntimePlatform.OSXEditor || Application.platform == RuntimePlatform.OSXPlayer) |
|||
modifier |= EventModifiers.Command; |
|||
else |
|||
modifier |= EventModifiers.Control; |
|||
} |
|||
shiftOnLastMatch = false; |
|||
} |
|||
|
|||
//atLeastModifier allow case were A is required but event provide shift+A
|
|||
public bool Match(IKeyboardEvent evt, bool atLeastForModifier = true) |
|||
{ |
|||
shiftOnLastMatch = evt.shiftKey; |
|||
if (atLeastForModifier) |
|||
return key == evt.keyCode && modifier == (evt.modifiers & modifier); |
|||
else |
|||
return key == evt.keyCode && modifier == evt.modifiers; |
|||
} |
|||
} |
|||
|
|||
struct TimeHelper |
|||
{ |
|||
long lastTime; |
|||
|
|||
public void Begin() => lastTime = System.DateTime.Now.Ticks; |
|||
|
|||
public float Update() |
|||
{ |
|||
float deltaTime = (System.DateTime.Now.Ticks - lastTime) / 10000000.0f; |
|||
lastTime = System.DateTime.Now.Ticks; |
|||
return deltaTime; |
|||
} |
|||
} |
|||
} |
|||
|
|||
class SwitchableCameraController : CameraController |
|||
{ |
|||
CameraState m_FirstView; |
|||
CameraState m_SecondView; |
|||
ViewIndex m_CurrentViewIndex; |
|||
|
|||
bool switchedDrag = false; |
|||
bool switchedWheel = false; |
|||
|
|||
public SwitchableCameraController(CameraState cameraStateFirstView, CameraState cameraStateSecondView, DisplayWindow window, Action<ViewIndex> focused) |
|||
: base(cameraStateFirstView, window, null) |
|||
{ |
|||
m_FirstView = cameraStateFirstView; |
|||
m_SecondView = cameraStateSecondView; |
|||
m_CurrentViewIndex = ViewIndex.First; |
|||
|
|||
m_Focused = () => focused?.Invoke(m_CurrentViewIndex); |
|||
} |
|||
|
|||
void SwitchTo(ViewIndex index) |
|||
{ |
|||
CameraState stateToSwitch; |
|||
switch (index) |
|||
{ |
|||
case ViewIndex.First: |
|||
stateToSwitch = m_FirstView; |
|||
break; |
|||
case ViewIndex.Second: |
|||
stateToSwitch = m_SecondView; |
|||
break; |
|||
default: |
|||
throw new ArgumentException("Unknown ViewIndex"); |
|||
} |
|||
|
|||
if (stateToSwitch != m_CameraState) |
|||
m_CameraState = stateToSwitch; |
|||
|
|||
m_CurrentViewIndex = index; |
|||
} |
|||
|
|||
public void SwitchUntilNextEndOfDrag() |
|||
{ |
|||
switchedDrag = true; |
|||
SwitchTo(ViewIndex.Second); |
|||
} |
|||
|
|||
override protected bool isDragging |
|||
{ |
|||
get => base.isDragging; |
|||
set |
|||
{ |
|||
bool switchBack = false; |
|||
if (switchedDrag && base.isDragging && !value) |
|||
switchBack = true; |
|||
base.isDragging = value; |
|||
if (switchBack) |
|||
SwitchTo(ViewIndex.First); |
|||
} |
|||
} |
|||
|
|||
public void SwitchUntilNextWheelEvent() |
|||
{ |
|||
switchedWheel = true; |
|||
SwitchTo(ViewIndex.Second); |
|||
} |
|||
|
|||
protected override void OnScrollWheel(WheelEvent evt) |
|||
{ |
|||
base.OnScrollWheel(evt); |
|||
if (switchedWheel) |
|||
SwitchTo(ViewIndex.First); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 65d4b8eee9b19324cb60b5b126ca55f4 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEditor.AnimatedValues; |
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Rendering.LookDev |
|||
{ |
|||
/// <summary>
|
|||
/// Interface to comunicate with simple <see cref="Renderer"/>
|
|||
/// </summary>
|
|||
public interface ICameraUpdater |
|||
{ |
|||
void UpdateCamera(Camera camera); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Class containing data regarding position, rotation and viewport size of a camera
|
|||
/// </summary>
|
|||
[System.Serializable] |
|||
public class CameraState : ICameraUpdater |
|||
{ |
|||
private static readonly Quaternion k_DefaultRotation = Quaternion.LookRotation(new Vector3(0.0f, 0.0f, 1.0f)); |
|||
private const float k_DefaultViewSize = 10f; |
|||
private static readonly Vector3 k_DefaultPivot = Vector3.zero; |
|||
private const float k_DefaultFoV = 90f; |
|||
private const float k_NearFactor = 0.000005f; |
|||
private const float k_MaxFar = 1000; |
|||
|
|||
/// <summary>The position of the camera pivot</summary>
|
|||
[field: SerializeField] |
|||
public Vector3 pivot { get; set; } = k_DefaultPivot; |
|||
|
|||
/// <summary>The rotation of the camera arround the pivot</summary>
|
|||
[field: SerializeField] |
|||
public Quaternion rotation { get; set; } = k_DefaultRotation; |
|||
|
|||
/// <summary>The size of the view</summary>
|
|||
[field: SerializeField] |
|||
public float viewSize { get; set; } = k_DefaultViewSize; |
|||
|
|||
/// <summary>The distance from pivot</summary>
|
|||
public float distanceFromPivot |
|||
// distance coeficient from vertical FOV should be
|
|||
// 1f / Mathf.Tan(kDefaultFoV * 0.5f * Mathf.Deg2Rad)
|
|||
// but with fixed FoV of 90, this coef is always equal to 1f
|
|||
=> viewSize; |
|||
|
|||
/// <summary>The position of the camera</summary>
|
|||
public Vector3 position |
|||
=> pivot + rotation * new Vector3(0, 0, -distanceFromPivot); |
|||
|
|||
/// <summary>The field of view of the camera</summary>
|
|||
public float fieldOfView => k_DefaultFoV; |
|||
|
|||
/// <summary>The far clip distance from camera</summary>
|
|||
public float farClip => Mathf.Max(k_MaxFar, 2 * k_MaxFar * viewSize); |
|||
|
|||
/// <summary>The near clip distance from camera</summary>
|
|||
public float nearClip => farClip * k_NearFactor; |
|||
|
|||
/// <summary>The Forward vector in world space</summary>
|
|||
public Vector3 forward => rotation * Vector3.forward; |
|||
|
|||
/// <summary>The Up vector in world space</summary>
|
|||
public Vector3 up => rotation * Vector3.up; |
|||
|
|||
/// <summary>The Right vector in world space</summary>
|
|||
public Vector3 right => rotation * Vector3.right; |
|||
|
|||
internal Vector3 QuickReprojectionWithFixedFOVOnPivotPlane(Rect screen, Vector2 screenPoint) |
|||
{ |
|||
if (screen.height == 0) |
|||
return Vector3.zero; |
|||
float aspect = screen.width / screen.height; |
|||
//Note: verticalDistance is same than distance from pivot with fixed FoV 90°
|
|||
float verticalDistance = distanceFromPivot; |
|||
Vector2 normalizedScreenPoint = new Vector2( |
|||
screenPoint.x * 2f / screen.width - 1f, |
|||
screenPoint.y * 2f / screen.height - 1f); |
|||
return pivot |
|||
- up * verticalDistance * normalizedScreenPoint.y |
|||
- right * verticalDistance * aspect * normalizedScreenPoint.x; |
|||
} |
|||
|
|||
//Pivot is always on center axis by construction
|
|||
internal Vector3 QuickProjectPivotInScreen(Rect screen) |
|||
=> new Vector3(screen.width * .5f, screen.height * .5f, distanceFromPivot); |
|||
|
|||
/// <summary>
|
|||
/// Update a Camera component and its transform with this state values
|
|||
/// </summary>
|
|||
/// <param name="camera">The camera to update</param>
|
|||
public void UpdateCamera(Camera camera) |
|||
{ |
|||
camera.transform.rotation = rotation; |
|||
camera.transform.position = position; |
|||
camera.nearClipPlane = nearClip; |
|||
camera.farClipPlane = farClip; |
|||
camera.fieldOfView = fieldOfView; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Reset the State to its default values
|
|||
/// </summary>
|
|||
public void Reset() |
|||
{ |
|||
pivot = k_DefaultPivot; |
|||
rotation = k_DefaultRotation; |
|||
viewSize = k_DefaultViewSize; |
|||
} |
|||
|
|||
internal void SynchronizeFrom(CameraState other) |
|||
{ |
|||
pivot = other.pivot; |
|||
rotation = other.rotation; |
|||
viewSize = other.viewSize; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: a517d1ef992c13d4f92edc2af5b3e6c0 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEngine.UIElements; |
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Rendering.LookDev |
|||
{ |
|||
//TODO: clamps to always have both node on screen
|
|||
class ComparisonGizmoController : Manipulator |
|||
{ |
|||
const float k_DragPadding = 0.05f; |
|||
const float k_ReferenceScale = 1080f; |
|||
|
|||
ComparisonGizmoState m_State; |
|||
SwitchableCameraController m_Switcher; |
|||
|
|||
enum Selected |
|||
{ |
|||
None, |
|||
NodeFirstView, |
|||
NodeSecondView, |
|||
PlaneSeparator, |
|||
Fader |
|||
} |
|||
Selected m_Selected; |
|||
|
|||
Vector2 m_SavedRelativePositionOnMouseDown; |
|||
bool m_IsDragging; |
|||
|
|||
bool isDragging |
|||
{ |
|||
get => m_IsDragging; |
|||
set |
|||
{ |
|||
//As in scene view, stop dragging as first button is release in case of multiple button down
|
|||
if (value ^ m_IsDragging) |
|||
{ |
|||
if (value) |
|||
{ |
|||
target.RegisterCallback<MouseMoveEvent>(OnMouseDrag); |
|||
target.CaptureMouse(); |
|||
} |
|||
else |
|||
{ |
|||
target.ReleaseMouse(); |
|||
target.UnregisterCallback<MouseMoveEvent>(OnMouseDrag); |
|||
} |
|||
m_IsDragging = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public ComparisonGizmoController(ComparisonGizmoState state, SwitchableCameraController switcher) |
|||
{ |
|||
m_State = state; |
|||
m_Switcher = switcher; |
|||
} |
|||
|
|||
protected override void RegisterCallbacksOnTarget() |
|||
{ |
|||
target.RegisterCallback<MouseDownEvent>(OnMouseDown); |
|||
target.RegisterCallback<MouseUpEvent>(OnMouseUp); |
|||
target.RegisterCallback<WheelEvent>(OnScrollWheel); |
|||
} |
|||
|
|||
protected override void UnregisterCallbacksFromTarget() |
|||
{ |
|||
target.UnregisterCallback<MouseDownEvent>(OnMouseDown); |
|||
target.UnregisterCallback<MouseUpEvent>(OnMouseUp); |
|||
target.UnregisterCallback<WheelEvent>(OnScrollWheel); |
|||
} |
|||
|
|||
void OnScrollWheel(WheelEvent evt) |
|||
{ |
|||
if (LookDev.currentContext.layout.viewLayout != Layout.CustomSplit) |
|||
return; |
|||
if (GetViewFromComposition(evt.localMousePosition) == ViewIndex.Second) |
|||
m_Switcher.SwitchUntilNextWheelEvent(); |
|||
//let event be propagated to views
|
|||
} |
|||
|
|||
void OnMouseDown(MouseDownEvent evt) |
|||
{ |
|||
if (LookDev.currentContext.layout.viewLayout != Layout.CustomSplit) |
|||
return; |
|||
|
|||
Rect displayRect = target.contentRect; |
|||
SelectGizmoZone(GetNormalizedCoordinates(evt.localMousePosition, displayRect)); |
|||
if (m_Selected != Selected.None) |
|||
{ |
|||
m_SavedRelativePositionOnMouseDown = GetNormalizedCoordinates(evt.localMousePosition, displayRect) - m_State.center; |
|||
isDragging = true; |
|||
//We do not want to move camera and gizmo at the same time.
|
|||
evt.StopImmediatePropagation(); |
|||
} |
|||
else |
|||
{ |
|||
//else let event be propagated to views
|
|||
if (GetViewFromComposition(evt.localMousePosition) == ViewIndex.Second) |
|||
m_Switcher.SwitchUntilNextEndOfDrag(); |
|||
} |
|||
} |
|||
|
|||
void OnMouseUp(MouseUpEvent evt) |
|||
{ |
|||
if (LookDev.currentContext.layout.viewLayout != Layout.CustomSplit |
|||
|| m_Selected == Selected.None) |
|||
return; |
|||
|
|||
// deadzone in fader gizmo
|
|||
if (m_Selected == Selected.Fader && Mathf.Abs(m_State.blendFactor) < ComparisonGizmoState.circleRadiusSelected / (m_State.length - ComparisonGizmoState.circleRadius)) |
|||
m_State.blendFactor = 0f; |
|||
|
|||
m_Selected = Selected.None; |
|||
isDragging = false; |
|||
//We do not want to move camera and gizmo at the same time.
|
|||
evt.StopImmediatePropagation(); |
|||
|
|||
LookDev.SaveConfig(); |
|||
} |
|||
|
|||
void OnMouseDrag(MouseMoveEvent evt) |
|||
{ |
|||
if (m_Selected == Selected.None) |
|||
return; |
|||
|
|||
switch (m_Selected) |
|||
{ |
|||
case Selected.PlaneSeparator: OnDragPlaneSeparator(evt); break; |
|||
case Selected.NodeFirstView: |
|||
case Selected.NodeSecondView: OnDragPlaneNodeExtremity(evt); break; |
|||
case Selected.Fader: OnDragFader(evt); break; |
|||
default: throw new ArgumentException("Unknown kind of Selected"); |
|||
} |
|||
} |
|||
|
|||
void OnDragPlaneSeparator(MouseMoveEvent evt) |
|||
{ |
|||
//TODO: handle case when resizing window (clamping)
|
|||
Vector2 newPosition = GetNormalizedCoordinates(evt.localMousePosition, target.contentRect) - m_SavedRelativePositionOnMouseDown; |
|||
|
|||
// We clamp the center of the gizmo to the border of the screen in order to avoid being able to put it out of the screen.
|
|||
// The safe band is here to ensure that you always see at least part of the gizmo in order to be able to grab it again.
|
|||
//Vector2 extends = GetNormalizedCoordinates(new Vector2(displayRect.width, displayRect.height), displayRect);
|
|||
//newPosition.x = Mathf.Clamp(newPosition.x, -extends.x + k_DragPadding, extends.x - k_DragPadding);
|
|||
//newPosition.y = Mathf.Clamp(newPosition.y, -extends.y + k_DragPadding, extends.y - k_DragPadding);
|
|||
|
|||
m_State.Update(newPosition, m_State.length, m_State.angle); |
|||
//We do not want to move camera and gizmo at the same time.
|
|||
evt.StopImmediatePropagation(); |
|||
} |
|||
|
|||
void OnDragPlaneNodeExtremity(MouseMoveEvent evt) |
|||
{ |
|||
Vector2 normalizedCoord = GetNormalizedCoordinates(evt.localMousePosition, target.contentRect); |
|||
Vector2 basePoint, newPoint; |
|||
float angleSnapping = Mathf.Deg2Rad * 45.0f * 0.5f; |
|||
|
|||
newPoint = normalizedCoord; |
|||
basePoint = m_Selected == Selected.NodeFirstView ? m_State.point2 : m_State.point1; |
|||
|
|||
// Snap to a multiple of "angleSnapping"
|
|||
if ((evt.modifiers & EventModifiers.Shift) != 0) |
|||
{ |
|||
Vector3 verticalPlane = new Vector3(-1.0f, 0.0f, basePoint.x); |
|||
float side = Vector3.Dot(new Vector3(normalizedCoord.x, normalizedCoord.y, 1.0f), verticalPlane); |
|||
|
|||
float angle = Mathf.Deg2Rad * Vector2.Angle(new Vector2(0.0f, 1.0f), normalizedCoord - basePoint); |
|||
if (side > 0.0f) |
|||
angle = 2.0f * Mathf.PI - angle; |
|||
angle = (int)(angle / angleSnapping) * angleSnapping; |
|||
Vector2 dir = normalizedCoord - basePoint; |
|||
float length = dir.magnitude; |
|||
newPoint = basePoint + new Vector2(Mathf.Sin(angle), Mathf.Cos(angle)) * length; |
|||
} |
|||
|
|||
if (m_Selected == Selected.NodeFirstView) |
|||
m_State.Update(newPoint, basePoint); |
|||
else |
|||
m_State.Update(basePoint, newPoint); |
|||
//We do not want to move camera and gizmo at the same time.
|
|||
evt.StopImmediatePropagation(); |
|||
} |
|||
|
|||
void OnDragFader(MouseMoveEvent evt) |
|||
{ |
|||
Vector2 mousePosition = GetNormalizedCoordinates(evt.localMousePosition, target.contentRect); |
|||
float distanceToOrthoPlane = -Vector3.Dot(new Vector3(mousePosition.x, mousePosition.y, 1.0f), m_State.planeOrtho) / m_State.blendFactorMaxGizmoDistance; |
|||
m_State.blendFactor = Mathf.Clamp(distanceToOrthoPlane, -1.0f, 1.0f); |
|||
//We do not want to move camera and gizmo at the same time.
|
|||
evt.StopImmediatePropagation(); |
|||
} |
|||
|
|||
void SelectGizmoZone(Vector2 normalizedMousePosition) |
|||
{ |
|||
//TODO: Optimize
|
|||
Vector3 normalizedMousePositionZ1 = new Vector3(normalizedMousePosition.x, normalizedMousePosition.y, 1.0f); |
|||
float distanceToPlane = Vector3.Dot(normalizedMousePositionZ1, m_State.plane); |
|||
float absDistanceToPlane = Mathf.Abs(distanceToPlane); |
|||
float distanceFromCenter = Vector2.Distance(normalizedMousePosition, m_State.center); |
|||
float distanceToOrtho = Vector3.Dot(normalizedMousePositionZ1, m_State.planeOrtho); |
|||
float side = (distanceToOrtho > 0.0f) ? 1.0f : -1.0f; |
|||
Vector2 orthoPlaneNormal = new Vector2(m_State.planeOrtho.x, m_State.planeOrtho.y); |
|||
|
|||
Selected selected = Selected.None; |
|||
if (absDistanceToPlane < ComparisonGizmoState.circleRadiusSelected && (distanceFromCenter < (m_State.length + ComparisonGizmoState.circleRadiusSelected))) |
|||
{ |
|||
if (absDistanceToPlane < ComparisonGizmoState.thicknessSelected) |
|||
selected = Selected.PlaneSeparator; |
|||
|
|||
Vector2 circleCenter = m_State.center + side * orthoPlaneNormal * m_State.length; |
|||
float d = Vector2.Distance(normalizedMousePosition, circleCenter); |
|||
if (d <= ComparisonGizmoState.circleRadiusSelected) |
|||
selected = side > 0.0f ? Selected.NodeFirstView : Selected.NodeSecondView; |
|||
|
|||
float maxBlendCircleDistanceToCenter = m_State.blendFactorMaxGizmoDistance; |
|||
float blendCircleDistanceToCenter = m_State.blendFactor * maxBlendCircleDistanceToCenter; |
|||
Vector2 blendCircleCenter = m_State.center - orthoPlaneNormal * blendCircleDistanceToCenter; |
|||
float blendCircleSelectionRadius = Mathf.Lerp(ComparisonGizmoState.blendFactorCircleRadius, ComparisonGizmoState.blendFactorCircleRadiusSelected, Mathf.Clamp((maxBlendCircleDistanceToCenter - Mathf.Abs(blendCircleDistanceToCenter)) / (ComparisonGizmoState.blendFactorCircleRadiusSelected - ComparisonGizmoState.blendFactorCircleRadius), 0.0f, 1.0f)); |
|||
if ((normalizedMousePosition - blendCircleCenter).magnitude < blendCircleSelectionRadius) |
|||
selected = Selected.Fader; |
|||
} |
|||
|
|||
m_Selected = selected; |
|||
} |
|||
|
|||
//normalize in [-1,1]^2 for a 1080^2. Can be above 1 for higher than 1080.
|
|||
internal static Vector2 GetNormalizedCoordinates(Vector2 localMousePosition, Rect rect) |
|||
=> new Vector2( |
|||
(2f * localMousePosition.x - rect.width) / k_ReferenceScale, |
|||
(-2f * localMousePosition.y + rect.height) / k_ReferenceScale); |
|||
|
|||
ViewIndex GetViewFromComposition(Vector2 localCoordinate) |
|||
{ |
|||
Vector2 normalizedLocalCoordinate = GetNormalizedCoordinates(localCoordinate, target.contentRect); |
|||
return Vector3.Dot(new Vector3(normalizedLocalCoordinate.x, normalizedLocalCoordinate.y, 1), m_State.plane) >= 0 |
|||
? ViewIndex.First |
|||
: ViewIndex.Second; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: c775302533fffe2449b5b27bab30a56c |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEditor.AnimatedValues; |
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Rendering.LookDev |
|||
{ |
|||
[Serializable] |
|||
public class ComparisonGizmoState |
|||
{ |
|||
internal const float thickness = 0.0028f; |
|||
internal const float thicknessSelected = 0.015f; |
|||
internal const float circleRadius = 0.014f; |
|||
internal const float circleRadiusSelected = 0.03f; |
|||
internal const float blendFactorCircleRadius = 0.01f; |
|||
internal const float blendFactorCircleRadiusSelected = 0.03f; |
|||
|
|||
/// <summary>Position of the first extremity</summary>
|
|||
public Vector2 point1 { get; private set; } |
|||
/// <summary>Position of the second extremity</summary>
|
|||
public Vector2 point2 { get; private set; } |
|||
/// <summary>Position of the center</summary>
|
|||
[field: SerializeField] |
|||
public Vector2 center { get; private set; } = Vector2.zero; |
|||
/// <summary>Angle from vertical in radian</summary>
|
|||
[field: SerializeField] |
|||
public float angle { get; private set; } |
|||
/// <summary>Length between extremity</summary>
|
|||
[field: SerializeField] |
|||
public float length { get; private set; } = 0.2f; |
|||
internal Vector4 plane { get; private set; } |
|||
internal Vector4 planeOrtho { get; private set; } |
|||
/// <summary>
|
|||
/// The position of the blending slider.
|
|||
/// From value -1 on first extremity to value 1 on second extremity.
|
|||
/// </summary>
|
|||
[field: SerializeField] |
|||
public float blendFactor { get; set; } |
|||
|
|||
internal float blendFactorMaxGizmoDistance |
|||
=> length - circleRadius - blendFactorCircleRadius; |
|||
|
|||
internal float blendFactorMinGizmoDistance |
|||
=> length - circleRadius - blendFactorCircleRadiusSelected; |
|||
|
|||
internal void Init() |
|||
=> Update(center, length, angle); |
|||
|
|||
//TODO: optimize
|
|||
private Vector4 Get2DPlane(Vector2 firstPoint, float angle) |
|||
{ |
|||
Vector4 result = new Vector4(); |
|||
angle = angle % (2.0f * (float)Math.PI); |
|||
Vector2 secondPoint = new Vector2(firstPoint.x + Mathf.Sin(angle), firstPoint.y + Mathf.Cos(angle)); |
|||
Vector2 diff = secondPoint - firstPoint; |
|||
if (Mathf.Abs(diff.x) < 1e-5) |
|||
{ |
|||
result.Set(-1.0f, 0.0f, firstPoint.x, 0.0f); |
|||
float sign = Mathf.Cos(angle) > 0.0f ? 1.0f : -1.0f; |
|||
result *= sign; |
|||
} |
|||
else |
|||
{ |
|||
float slope = diff.y / diff.x; |
|||
result.Set(-slope, 1.0f, -(firstPoint.y - slope * firstPoint.x), 0.0f); |
|||
} |
|||
|
|||
if (angle > Mathf.PI) |
|||
result = -result; |
|||
|
|||
float length = Mathf.Sqrt(result.x * result.x + result.y * result.y); |
|||
result = result / length; |
|||
return result; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Update all fields while moving one extremity
|
|||
/// </summary>
|
|||
/// <param name="point1">The new first extremity position</param>
|
|||
/// <param name="point2">The new second extremity position</param>
|
|||
public void Update(Vector2 point1, Vector2 point2) |
|||
{ |
|||
this.point1 = point1; |
|||
this.point2 = point2; |
|||
center = (point1 + point2) * 0.5f; |
|||
length = (point2 - point1).magnitude * 0.5f; |
|||
|
|||
Vector3 verticalPlane = Get2DPlane(center, 0.0f); |
|||
float side = Vector3.Dot(new Vector3(point1.x, point1.y, 1.0f), verticalPlane); |
|||
angle = (Mathf.Deg2Rad * Vector2.Angle(new Vector2(0.0f, 1.0f), (point1 - point2).normalized)); |
|||
if (side > 0.0f) |
|||
angle = 2.0f * Mathf.PI - angle; |
|||
|
|||
plane = Get2DPlane(center, angle); |
|||
planeOrtho = Get2DPlane(center, angle + 0.5f * (float)Mathf.PI); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Update all fields while moving the bar
|
|||
/// </summary>
|
|||
/// <param name="center">The new center position</param>
|
|||
/// <param name="length">Tne new length of this gizmo</param>
|
|||
/// <param name="angle">The new angle of this gizmo</param>
|
|||
public void Update(Vector2 center, float length, float angle) |
|||
{ |
|||
this.center = center; |
|||
this.length = length; |
|||
this.angle = angle; |
|||
|
|||
plane = Get2DPlane(center, angle); |
|||
planeOrtho = Get2DPlane(center, angle + 0.5f * (float)Mathf.PI); |
|||
|
|||
Vector2 dir = new Vector2(planeOrtho.x, planeOrtho.y); |
|||
point1 = center + dir * length; |
|||
point2 = center - dir * length; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: b1b1fb8ac8cbdcb4aac5fa767e4c4330 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEngine; |
|||
using UnityEngine.Experimental.Rendering; |
|||
using IDataProvider = UnityEngine.Rendering.LookDev.IDataProvider; |
|||
|
|||
namespace UnityEditor.Rendering.LookDev |
|||
{ |
|||
enum ShadowCompositionPass |
|||
{ |
|||
WithSun, |
|||
WithoutSun, |
|||
ShadowMask |
|||
} |
|||
|
|||
enum CompositionFinal |
|||
{ |
|||
First, |
|||
Second |
|||
} |
|||
|
|||
class RenderTextureCache : IDisposable |
|||
{ |
|||
//RenderTextures are packed this way:
|
|||
//0: ViewIndex.First, ShadowCompositionPass.WithSun
|
|||
//1: ViewIndex.First, ShadowCompositionPass.WithoutSun
|
|||
//2: ViewIndex.First, ShadowCompositionPass.ShadowMask
|
|||
//3: CompositionFinal.First
|
|||
//4: ViewIndex.Second, ShadowCompositionPass.WithSun
|
|||
//5: ViewIndex.Second, ShadowCompositionPass.WithoutSun
|
|||
//6: ViewIndex.Second, ShadowCompositionPass.ShadowMask
|
|||
//7: CompositionFinal.Second
|
|||
RenderTexture[] m_RTs = new RenderTexture[8]; |
|||
|
|||
public RenderTexture this[ViewIndex index, ShadowCompositionPass passIndex] |
|||
{ |
|||
get => m_RTs[computeIndex(index, passIndex)]; |
|||
set => m_RTs[computeIndex(index, passIndex)] = value; |
|||
} |
|||
|
|||
public RenderTexture this[CompositionFinal index] |
|||
{ |
|||
get => m_RTs[computeIndex(index)]; |
|||
set => m_RTs[computeIndex(index)] = value; |
|||
} |
|||
|
|||
int computeIndex(ViewIndex index, ShadowCompositionPass passIndex) |
|||
=> (int)index * 4 + (int)(passIndex); |
|||
int computeIndex(CompositionFinal index) |
|||
=> 3 + (int)(index) * 4; |
|||
|
|||
void UpdateSize(int index, Rect rect, bool pixelPerfect, Camera renderingCamera, string renderDocName = "LookDevRT") |
|||
{ |
|||
bool nullRect = rect.IsNullOrInverted(); |
|||
|
|||
GraphicsFormat format = SystemInfo.IsFormatSupported(GraphicsFormat.R16G16B16A16_SFloat, FormatUsage.Render) |
|||
? GraphicsFormat.R16G16B16A16_SFloat |
|||
: SystemInfo.GetGraphicsFormat(DefaultFormat.LDR); |
|||
if (m_RTs[index] != null && (nullRect || m_RTs[index].graphicsFormat != format)) |
|||
{ |
|||
m_RTs[index].Release(); |
|||
UnityEngine.Object.DestroyImmediate(m_RTs[index]); |
|||
m_RTs[index] = null; |
|||
} |
|||
if (nullRect) |
|||
return; |
|||
|
|||
int width = (int)rect.width; |
|||
int height = (int)rect.height; |
|||
|
|||
if (m_RTs[index] == null) |
|||
{ |
|||
m_RTs[index] = new RenderTexture(0, 0, 24, format); |
|||
m_RTs[index].name = renderDocName; |
|||
m_RTs[index].antiAliasing = 1; |
|||
m_RTs[index].hideFlags = HideFlags.HideAndDontSave; |
|||
} |
|||
|
|||
if (m_RTs[index].width != width || m_RTs[index].height != height) |
|||
{ |
|||
m_RTs[index].Release(); |
|||
m_RTs[index].width = width; |
|||
m_RTs[index].height = height; |
|||
m_RTs[index].Create(); |
|||
} |
|||
|
|||
if (renderingCamera != null) |
|||
renderingCamera.targetTexture = m_RTs[index]; |
|||
} |
|||
|
|||
public void UpdateSize(Rect rect, ViewIndex index, bool pixelPerfect, Camera renderingCamera) |
|||
{ |
|||
UpdateSize(computeIndex(index, ShadowCompositionPass.WithSun), rect, pixelPerfect, renderingCamera, $"LookDevRT-{index}-WithSun"); |
|||
UpdateSize(computeIndex(index, ShadowCompositionPass.WithoutSun), rect, pixelPerfect, renderingCamera, $"LookDevRT-{index}-WithoutSun"); |
|||
UpdateSize(computeIndex(index, ShadowCompositionPass.ShadowMask), rect, pixelPerfect, renderingCamera, $"LookDevRT-{index}-ShadowMask"); |
|||
} |
|||
|
|||
|
|||
public void UpdateSize(Rect rect, CompositionFinal index, bool pixelPerfect, Camera renderingCamera) |
|||
=> UpdateSize(computeIndex(index), rect, pixelPerfect, renderingCamera, $"LookDevRT-Final-{index}"); |
|||
|
|||
bool m_Disposed = false; |
|||
public void Dispose() |
|||
{ |
|||
if (m_Disposed) |
|||
return; |
|||
m_Disposed = true; |
|||
|
|||
for (int index = 0; index < 8; ++index) |
|||
{ |
|||
if (m_RTs[index] == null || m_RTs[index].Equals(null)) |
|||
continue; |
|||
|
|||
UnityEngine.Object.DestroyImmediate(m_RTs[index]); |
|||
m_RTs[index] = null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
class Compositer : IDisposable |
|||
{ |
|||
public static readonly Color firstViewGizmoColor = new Color32(0, 154, 154, 255); |
|||
public static readonly Color secondViewGizmoColor = new Color32(255, 37, 4, 255); |
|||
static Material s_Material; |
|||
static Material material |
|||
{ |
|||
get |
|||
{ |
|||
if (s_Material == null || s_Material.Equals(null)) |
|||
s_Material = new Material(Shader.Find("Hidden/LookDev/Compositor")); |
|||
return s_Material; |
|||
} |
|||
} |
|||
|
|||
IDataProvider m_DataProvider; |
|||
IViewDisplayer m_Displayer; |
|||
Context m_Contexts; |
|||
RenderTextureCache m_RenderTextures = new RenderTextureCache(); |
|||
Renderer m_Renderer = new Renderer(); |
|||
RenderingData[] m_RenderDataCache; |
|||
|
|||
bool m_pixelPerfect; |
|||
bool m_Disposed; |
|||
|
|||
public bool pixelPerfect |
|||
{ |
|||
get => m_pixelPerfect; |
|||
set => m_Renderer.pixelPerfect = m_pixelPerfect = value; |
|||
} |
|||
|
|||
Color m_AmbientColor = new Color(0.0f, 0.0f, 0.0f, 0.0f); |
|||
|
|||
bool m_RenderDocAcquisitionRequested; |
|||
|
|||
public Compositer( |
|||
IViewDisplayer displayer, |
|||
Context contexts, |
|||
IDataProvider dataProvider, |
|||
StageCache stages) |
|||
{ |
|||
m_DataProvider = dataProvider; |
|||
m_Displayer = displayer; |
|||
m_Contexts = contexts; |
|||
|
|||
m_RenderDataCache = new RenderingData[2] |
|||
{ |
|||
new RenderingData() { stage = stages[ViewIndex.First], updater = contexts.GetViewContent(ViewIndex.First).camera }, |
|||
new RenderingData() { stage = stages[ViewIndex.Second], updater = contexts.GetViewContent(ViewIndex.Second).camera } |
|||
}; |
|||
|
|||
m_Displayer.OnRenderDocAcquisitionTriggered += RenderDocAcquisitionRequested; |
|||
m_Displayer.OnUpdateRequested += Render; |
|||
} |
|||
|
|||
void RenderDocAcquisitionRequested() |
|||
=> m_RenderDocAcquisitionRequested = true; |
|||
|
|||
void CleanUp() |
|||
{ |
|||
for (int index = 0; index < 2; ++index) |
|||
{ |
|||
m_RenderDataCache[index]?.Dispose(); |
|||
m_RenderDataCache[index] = null; |
|||
} |
|||
|
|||
m_RenderTextures.Dispose(); |
|||
|
|||
m_Displayer.OnRenderDocAcquisitionTriggered -= RenderDocAcquisitionRequested; |
|||
m_Displayer.OnUpdateRequested -= Render; |
|||
} |
|||
public void Dispose() |
|||
{ |
|||
if (m_Disposed) |
|||
return; |
|||
m_Disposed = true; |
|||
CleanUp(); |
|||
GC.SuppressFinalize(this); |
|||
} |
|||
~Compositer() => CleanUp(); |
|||
|
|||
public void Render() |
|||
{ |
|||
//TODO: make integration EditorWindow agnostic!
|
|||
if (UnityEditorInternal.RenderDoc.IsLoaded() && UnityEditorInternal.RenderDoc.IsSupported() && m_RenderDocAcquisitionRequested) |
|||
UnityEditorInternal.RenderDoc.BeginCaptureRenderDoc(m_Displayer as EditorWindow); |
|||
|
|||
using (new UnityEngine.Rendering.VolumeIsolationScope(true)) |
|||
{ |
|||
switch (m_Contexts.layout.viewLayout) |
|||
{ |
|||
case Layout.FullFirstView: |
|||
RenderSingleAndOutput(ViewIndex.First); |
|||
break; |
|||
case Layout.FullSecondView: |
|||
RenderSingleAndOutput(ViewIndex.Second); |
|||
break; |
|||
case Layout.HorizontalSplit: |
|||
case Layout.VerticalSplit: |
|||
RenderSingleAndOutput(ViewIndex.First); |
|||
RenderSingleAndOutput(ViewIndex.Second); |
|||
break; |
|||
case Layout.CustomSplit: |
|||
RenderCompositeAndOutput(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
//TODO: make integration EditorWindow agnostic!
|
|||
if (UnityEditorInternal.RenderDoc.IsLoaded() && UnityEditorInternal.RenderDoc.IsSupported() && m_RenderDocAcquisitionRequested) |
|||
UnityEditorInternal.RenderDoc.EndCaptureRenderDoc(m_Displayer as EditorWindow); |
|||
|
|||
//stating that RenderDoc do not need to acquire anymore should
|
|||
//allows to gather both view and composition in render doc at once
|
|||
m_RenderDocAcquisitionRequested = false; |
|||
} |
|||
|
|||
void AcquireDataForView(ViewIndex index, Rect viewport) |
|||
{ |
|||
var renderingData = m_RenderDataCache[(int)index]; |
|||
renderingData.viewPort = viewport; |
|||
Environment env = m_Contexts.GetViewContent(index).environment; |
|||
|
|||
m_RenderTextures.UpdateSize(renderingData.viewPort, index, m_Renderer.pixelPerfect, renderingData.stage.camera); |
|||
|
|||
renderingData.output = m_RenderTextures[index, ShadowCompositionPass.WithSun]; |
|||
m_Renderer.Acquire(renderingData, RenderingPass.First); |
|||
|
|||
//get shadowmask betwen first and last pass to still be isolated
|
|||
RenderTexture tmp = m_RenderTextures[index, ShadowCompositionPass.ShadowMask]; |
|||
env?.UpdateSunPosition(renderingData.stage.sunLight); |
|||
renderingData.stage.sunLight.intensity = 1f; |
|||
m_DataProvider.GetShadowMask(ref tmp, renderingData.stage.runtimeInterface); |
|||
renderingData.stage.sunLight.intensity = 0f; |
|||
m_RenderTextures[index, ShadowCompositionPass.ShadowMask] = tmp; |
|||
|
|||
if (env != null) |
|||
m_DataProvider.UpdateSky(renderingData.stage.camera, env.shadowSky, renderingData.stage.runtimeInterface); |
|||
renderingData.output = m_RenderTextures[index, ShadowCompositionPass.WithoutSun]; |
|||
m_Renderer.Acquire(renderingData, RenderingPass.Last); |
|||
|
|||
if (env != null) |
|||
m_DataProvider.UpdateSky(renderingData.stage.camera, env.sky, renderingData.stage.runtimeInterface); |
|||
} |
|||
|
|||
void RenderSingleAndOutput(ViewIndex index) |
|||
{ |
|||
Rect viewport = m_Displayer.GetRect((ViewCompositionIndex)index); |
|||
AcquireDataForView(index, viewport); |
|||
Compositing(viewport, (int)index, (CompositionFinal)index); |
|||
m_Displayer.SetTexture((ViewCompositionIndex)index, m_RenderTextures[(CompositionFinal)index]); |
|||
} |
|||
|
|||
void RenderCompositeAndOutput() |
|||
{ |
|||
Rect viewport = m_Displayer.GetRect(ViewCompositionIndex.Composite); |
|||
|
|||
AcquireDataForView(ViewIndex.First, viewport); |
|||
AcquireDataForView(ViewIndex.Second, viewport); |
|||
Compositing(viewport, 2 /*split*/, CompositionFinal.First); |
|||
m_Displayer.SetTexture(ViewCompositionIndex.Composite, m_RenderTextures[CompositionFinal.First]); |
|||
} |
|||
|
|||
void Compositing(Rect rect, int pass, CompositionFinal finalBufferIndex) |
|||
{ |
|||
if (rect.IsNullOrInverted() |
|||
|| (m_Contexts.layout.viewLayout != Layout.FullSecondView |
|||
&& (m_RenderTextures[ViewIndex.First, ShadowCompositionPass.WithSun] == null |
|||
|| m_RenderTextures[ViewIndex.First, ShadowCompositionPass.WithoutSun] == null |
|||
|| m_RenderTextures[ViewIndex.First, ShadowCompositionPass.ShadowMask] == null)) |
|||
|| (m_Contexts.layout.viewLayout != Layout.FullFirstView |
|||
&& (m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.WithSun] == null |
|||
|| m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.WithoutSun] == null |
|||
|| m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.ShadowMask] == null))) |
|||
{ |
|||
m_RenderTextures[finalBufferIndex] = null; |
|||
return; |
|||
} |
|||
|
|||
m_RenderTextures.UpdateSize(rect, finalBufferIndex, m_pixelPerfect, null); |
|||
|
|||
ComparisonGizmoState gizmo = m_Contexts.layout.gizmoState; |
|||
|
|||
Vector4 gizmoPosition = new Vector4(gizmo.center.x, gizmo.center.y, 0.0f, 0.0f); |
|||
Vector4 gizmoZoneCenter = new Vector4(gizmo.point2.x, gizmo.point2.y, 0.0f, 0.0f); |
|||
Vector4 gizmoThickness = new Vector4(ComparisonGizmoState.thickness, ComparisonGizmoState.thicknessSelected, 0.0f, 0.0f); |
|||
Vector4 gizmoCircleRadius = new Vector4(ComparisonGizmoState.circleRadius, ComparisonGizmoState.circleRadiusSelected, 0.0f, 0.0f); |
|||
|
|||
Environment env0 = m_Contexts.GetViewContent(ViewIndex.First).environment; |
|||
Environment env1 = m_Contexts.GetViewContent(ViewIndex.Second).environment; |
|||
|
|||
float exposureValue0 = env0?.sky.exposure ?? 0f; |
|||
float exposureValue1 = env1?.sky.exposure ?? 0f; |
|||
float dualViewBlendFactor = gizmo.blendFactor; |
|||
float isCurrentlyLeftEditting = m_Contexts.layout.lastFocusedView == ViewIndex.First ? 1f : -1f; |
|||
float dragAndDropContext = 0f; //1f left, -1f right, 0f neither
|
|||
float toneMapEnabled = -1f; //1f true, -1f false
|
|||
float shadowMultiplier0 = env0?.shadowIntensity ?? 0f; |
|||
float shadowMultiplier1 = env1?.shadowIntensity ?? 0f; |
|||
Color shadowColor0 = env0?.shadow.color ?? Color.white; |
|||
Color shadowColor1 = env1?.shadow.color ?? Color.white; |
|||
|
|||
//TODO: handle shadow not at compositing step but in rendering
|
|||
Texture texWithSun0 = m_RenderTextures[ViewIndex.First, ShadowCompositionPass.WithSun]; |
|||
Texture texWithoutSun0 = m_RenderTextures[ViewIndex.First, ShadowCompositionPass.WithoutSun]; |
|||
Texture texShadowsMask0 = m_RenderTextures[ViewIndex.First, ShadowCompositionPass.ShadowMask]; |
|||
|
|||
Texture texWithSun1 = m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.WithSun]; |
|||
Texture texWithoutSun1 = m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.WithoutSun]; |
|||
Texture texShadowsMask1 = m_RenderTextures[ViewIndex.Second, ShadowCompositionPass.ShadowMask]; |
|||
|
|||
Vector4 compositingParams = new Vector4(dualViewBlendFactor, exposureValue0, exposureValue1, isCurrentlyLeftEditting); |
|||
Vector4 compositingParams2 = new Vector4(dragAndDropContext, toneMapEnabled, shadowMultiplier0, shadowMultiplier1); |
|||
|
|||
// Those could be tweakable for the neutral tonemapper, but in the case of the LookDev we don't need that
|
|||
const float k_BlackIn = 0.02f; |
|||
const float k_WhiteIn = 10.0f; |
|||
const float k_BlackOut = 0.0f; |
|||
const float k_WhiteOut = 10.0f; |
|||
const float k_WhiteLevel = 5.3f; |
|||
const float k_WhiteClip = 10.0f; |
|||
const float k_DialUnits = 20.0f; |
|||
const float k_HalfDialUnits = k_DialUnits * 0.5f; |
|||
const float k_GizmoRenderMode = 4f; //display all
|
|||
|
|||
// converting from artist dial units to easy shader-lerps (0-1)
|
|||
//TODO: to compute one time only
|
|||
Vector4 tonemapCoeff1 = new Vector4((k_BlackIn * k_DialUnits) + 1.0f, (k_BlackOut * k_HalfDialUnits) + 1.0f, (k_WhiteIn / k_DialUnits), (1.0f - (k_WhiteOut / k_DialUnits))); |
|||
Vector4 tonemapCoeff2 = new Vector4(0.0f, 0.0f, k_WhiteLevel, k_WhiteClip / k_HalfDialUnits); |
|||
|
|||
const float k_ReferenceScale = 1080.0f; |
|||
Vector4 screenRatio = new Vector4(rect.width / k_ReferenceScale, rect.height / k_ReferenceScale, rect.width, rect.height); |
|||
|
|||
RenderTexture oldActive = RenderTexture.active; |
|||
RenderTexture.active = m_RenderTextures[finalBufferIndex]; |
|||
material.SetTexture("_Tex0WithSun", texWithSun0); |
|||
material.SetTexture("_Tex0WithoutSun", texWithoutSun0); |
|||
material.SetTexture("_Tex0Shadows", texShadowsMask0); |
|||
material.SetColor("_ShadowColor0", shadowColor0); |
|||
material.SetTexture("_Tex1WithSun", texWithSun1); |
|||
material.SetTexture("_Tex1WithoutSun", texWithoutSun1); |
|||
material.SetTexture("_Tex1Shadows", texShadowsMask1); |
|||
material.SetColor("_ShadowColor1", shadowColor1); |
|||
material.SetVector("_CompositingParams", compositingParams); |
|||
material.SetVector("_CompositingParams2", compositingParams2); |
|||
material.SetColor("_FirstViewColor", firstViewGizmoColor); |
|||
material.SetColor("_SecondViewColor", secondViewGizmoColor); |
|||
material.SetVector("_GizmoPosition", gizmoPosition); |
|||
material.SetVector("_GizmoZoneCenter", gizmoZoneCenter); |
|||
material.SetVector("_GizmoSplitPlane", gizmo.plane); |
|||
material.SetVector("_GizmoSplitPlaneOrtho", gizmo.planeOrtho); |
|||
material.SetFloat("_GizmoLength", gizmo.length); |
|||
material.SetVector("_GizmoThickness", gizmoThickness); |
|||
material.SetVector("_GizmoCircleRadius", gizmoCircleRadius); |
|||
material.SetFloat("_BlendFactorCircleRadius", ComparisonGizmoState.blendFactorCircleRadius); |
|||
material.SetFloat("_GetBlendFactorMaxGizmoDistance", gizmo.blendFactorMaxGizmoDistance); |
|||
material.SetFloat("_GizmoRenderMode", k_GizmoRenderMode); |
|||
material.SetVector("_ScreenRatio", screenRatio); |
|||
material.SetVector("_ToneMapCoeffs1", tonemapCoeff1); |
|||
material.SetVector("_ToneMapCoeffs2", tonemapCoeff2); |
|||
material.SetPass(pass); |
|||
|
|||
Renderer.DrawFullScreenQuad(new Rect(0, 0, rect.width, rect.height)); |
|||
|
|||
RenderTexture.active = oldActive; |
|||
} |
|||
|
|||
public ViewIndex GetViewFromComposition(Vector2 localCoordinate) |
|||
{ |
|||
Rect compositionRect = m_Displayer.GetRect(ViewCompositionIndex.Composite); |
|||
Vector2 normalizedLocalCoordinate = ComparisonGizmoController.GetNormalizedCoordinates(localCoordinate, compositionRect); |
|||
switch (m_Contexts.layout.viewLayout) |
|||
{ |
|||
case Layout.CustomSplit: |
|||
return Vector3.Dot(new Vector3(normalizedLocalCoordinate.x, normalizedLocalCoordinate.y, 1), m_Contexts.layout.gizmoState.plane) >= 0 |
|||
? ViewIndex.First |
|||
: ViewIndex.Second; |
|||
default: |
|||
throw new Exception("GetViewFromComposition call when not inside a Composition"); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 5471cb5dd3020f24b9bc1644dc9c9ad3 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
Shader "Hidden/LookDev/Compositor" |
|||
{ |
|||
Properties |
|||
{ |
|||
_Tex0WithSun("First View", 2D) = "white" {} |
|||
_Tex0WithoutSun("First View without sun", 2D) = "white" {} |
|||
_Tex0Shadows("First View shadow mask", 2D) = "white" {} |
|||
_ShadowColor0("Shadow Color for first view", Color) = (1.0, 1.0, 1.0, 1.0) |
|||
_Tex1WithSun("Second View", 2D) = "white" {} |
|||
_Tex1WithoutSun("Second View without sun", 2D) = "white" {} |
|||
_Tex1Shadows("Second View shadow mask", 2D) = "white" {} |
|||
_ShadowColor1("Shadow Color for second view", Color) = (1.0, 1.0, 1.0, 1.0) |
|||
_CompositingParams("Blend Factor, exposure for first and second view, and current selected side", Vector) = (0.0, 1.0, 1.0, 1.0) |
|||
_CompositingParams2("Drag and drop zone and shadow multipliers", Vector) = (0.0, 1.0, 1.0, 1.0) // Drag and Drop zone Left == 1.0, Right == -1.0, None == 0.0 |
|||
_FirstViewColor("Gizmo Color for first view", Color) = (0.5, 0.5, 0.5, 0.5) |
|||
_SecondViewColor("Gizmo Color for second view", Color) = (0.5, 0.5, 0.5, 0.5) |
|||
_GizmoPosition("Position of split view gizmo", Vector) = (0.5, 0.5, 0.0, 0.0) |
|||
_GizmoZoneCenter("Center of Zone view gizmo", Vector) = (0.5, 0.5, 0.0, 0.0) |
|||
_GizmoSplitPlane("2D plane of the gizmo", Vector) = (1.0, 1.0, 0.0, 0.0) |
|||
_GizmoSplitPlaneOrtho("2D plane orthogonal to the gizmo", Vector) = (1.0, 1.0, 0.0, 0.0) |
|||
_GizmoLength("Gizmo Length", Float) = 0.2 |
|||
_GizmoThickness("Gizmo Thickness", Vector) = (0.01, 0.08, 0.0, 0.0) |
|||
_GizmoCircleRadius("Gizmo extremities radius", Vector) = (0.05, 0.4, 0.0, 0.0) |
|||
_GizmoRenderMode("Render gizmo mode", Float) = 0.0 |
|||
_GetBlendFactorMaxGizmoDistance("Distance on the gizmo where the blend circle stops", Float) = 0.2 |
|||
_BlendFactorCircleRadius("Visual radius of the blend factor gizmo", Float) = 0.01 |
|||
_ScreenRatio("Screen ratio", Vector) = (1.0, 1.0, 0.0, 0.0) // xy screen ratio, zw screen size |
|||
_ToneMapCoeffs1("Parameters for neutral tonemap", Vector) = (0.0, 0.0, 0.0, 0.0) |
|||
_ToneMapCoeffs2("Parameters for neutral tonemap", Vector) = (0.0, 0.0, 0.0, 0.0) |
|||
} |
|||
|
|||
CGINCLUDE |
|||
#include "UnityCG.cginc" |
|||
#pragma vertex vert |
|||
|
|||
// Enum matching GizmoOperationType in LookDevViews.cs |
|||
#define kNone 0.0f |
|||
#define kTranslation 1.0f |
|||
#define kRotationZone1 2.0f |
|||
#define kRotationZone2 3.0f |
|||
#define kAll 4.0f |
|||
|
|||
|
|||
sampler2D _Tex0WithSun; |
|||
sampler2D _Tex0WithoutSun; |
|||
sampler2D _Tex0Shadows; |
|||
float4 _ShadowColor0; |
|||
sampler2D _Tex1WithSun; |
|||
sampler2D _Tex1WithoutSun; |
|||
sampler2D _Tex1Shadows; |
|||
float4 _ShadowColor1; |
|||
float4 _CompositingParams; // x BlendFactor, yz ExposureValue (first/second view), w current selected side |
|||
float4 _CompositingParams2; // x current drag context, y apply tonemap (bool), z shadow multiplier |
|||
float4 _FirstViewColor; |
|||
float4 _SecondViewColor; |
|||
float4 _GizmoPosition; |
|||
float4 _GizmoZoneCenter; |
|||
float4 _GizmoThickness; |
|||
float4 _GizmoCircleRadius; |
|||
float4 _GizmoSplitPlane; |
|||
float4 _GizmoSplitPlaneOrtho; |
|||
float _GizmoLength; |
|||
float _GizmoRenderMode; |
|||
float _GetBlendFactorMaxGizmoDistance; |
|||
float _BlendFactorCircleRadius; |
|||
float4 _ScreenRatio; |
|||
float4 _ToneMapCoeffs1; |
|||
float4 _ToneMapCoeffs2; |
|||
|
|||
float4 _Tex0WithSun_ST; |
|||
|
|||
#define ShadowMultiplier0 _CompositingParams2.z |
|||
#define ShadowMultiplier1 _CompositingParams2.w |
|||
|
|||
#define ExposureValue1 _CompositingParams.y |
|||
#define ExposureValue2 _CompositingParams.z |
|||
|
|||
#define InBlack _ToneMapCoeffs1.x |
|||
#define OutBlack _ToneMapCoeffs1.y |
|||
#define InWhite _ToneMapCoeffs1.z |
|||
#define OutWhite _ToneMapCoeffs1.w |
|||
#define WhiteLevel _ToneMapCoeffs2.z |
|||
#define WhiteClip _ToneMapCoeffs2.w |
|||
|
|||
struct appdata_t |
|||
{ |
|||
float4 vertex : POSITION; |
|||
float2 texcoord : TEXCOORD0; |
|||
}; |
|||
|
|||
struct v2f |
|||
{ |
|||
float2 texcoord : TEXCOORD0; |
|||
float4 vertex : SV_POSITION; |
|||
}; |
|||
|
|||
float DistanceToSplit(float2 pos, float3 splitPlane) |
|||
{ |
|||
return dot(float3(pos, 1), splitPlane); |
|||
} |
|||
|
|||
bool IsInsideGizmo(float2 normalizedCoord, float absDistanceToPlane, float distanceFromCenter, float side, float3 orthoPlane, float gizmoCircleRadius, float gizmoThickness, out float outSmoothing, float mode) |
|||
{ |
|||
bool result = false; |
|||
outSmoothing = 0.0; |
|||
if (absDistanceToPlane < gizmoCircleRadius) // First "thick" bar, as large as the radius at extremities. |
|||
{ |
|||
if (distanceFromCenter < (_GizmoLength + gizmoCircleRadius)) |
|||
{ |
|||
// side < 0 is cyan circle, side > 0 is orange widget |
|||
if (mode == kAll || |
|||
(mode == kRotationZone1 && side > 0) || |
|||
(mode == kRotationZone2 && side < 0)) |
|||
{ |
|||
if (distanceFromCenter >= (_GizmoLength - gizmoCircleRadius)) // Inside circle at the extremities ? |
|||
{ |
|||
float2 circleCenter = _GizmoPosition.xy + side * orthoPlane.xy * _GizmoLength; |
|||
float d = length(normalizedCoord - circleCenter); |
|||
if (d <= gizmoCircleRadius) |
|||
{ |
|||
outSmoothing = smoothstep(1.0, 0.8, d / gizmoCircleRadius); |
|||
result = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (mode == kAll || mode == kTranslation) |
|||
{ |
|||
if (absDistanceToPlane < gizmoThickness && distanceFromCenter < _GizmoLength) |
|||
{ |
|||
outSmoothing = max(outSmoothing, smoothstep(1.0, 0.0, absDistanceToPlane / gizmoThickness)); |
|||
result = true; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
float4 GetGizmoColor(float2 normalizedCoord, float3 splitPlane, float3 orthoPlane) |
|||
{ |
|||
float distanceToPlane = DistanceToSplit(normalizedCoord, splitPlane); |
|||
float absDistanceToPlane = abs(distanceToPlane); |
|||
float distanceFromCenter = length(normalizedCoord.xy - _GizmoPosition.xy); |
|||
float distanceToOrtho = DistanceToSplit(normalizedCoord, orthoPlane); |
|||
|
|||
float4 result = float4(0.0, 0.0, 0.0, 0.0); |
|||
float side = 0.0; |
|||
if (distanceToOrtho > 0.0) |
|||
{ |
|||
result.rgb = _FirstViewColor.rgb; |
|||
side = 1.0; |
|||
} |
|||
else |
|||
{ |
|||
result.rgb = _SecondViewColor.rgb; |
|||
side = -1.0; |
|||
} |
|||
|
|||
result.a = 0.0; |
|||
|
|||
// "normal" gizmo |
|||
float smoothing = 1.0; |
|||
if (IsInsideGizmo(normalizedCoord, absDistanceToPlane, distanceFromCenter, side, orthoPlane, _GizmoCircleRadius.x, _GizmoThickness.x, smoothing, kAll)) |
|||
{ |
|||
result.a = 1.0 * smoothing; |
|||
} |
|||
|
|||
// large gizmo when in translation mode |
|||
if (IsInsideGizmo(normalizedCoord, absDistanceToPlane, distanceFromCenter, side, orthoPlane, _GizmoCircleRadius.y, _GizmoThickness.y, smoothing, _GizmoRenderMode)) |
|||
{ |
|||
result.a = max(result.a, 0.25 * smoothing); |
|||
} |
|||
|
|||
// Blend factor selection disc |
|||
float2 blendCircleCenter = _GizmoPosition.xy - _CompositingParams.x * orthoPlane.xy * _GetBlendFactorMaxGizmoDistance; |
|||
float distanceToBlendCircle = length(normalizedCoord.xy - blendCircleCenter); |
|||
if (distanceToBlendCircle < _BlendFactorCircleRadius) |
|||
{ |
|||
float alpha = smoothstep(1.0, 0.6, distanceToBlendCircle / _BlendFactorCircleRadius); |
|||
result = lerp(result, float4(1.0, 1.0, 1.0, alpha), alpha); |
|||
} |
|||
|
|||
// Display transparent disc if near the center where the blend factor selection disc will automatically snap back |
|||
if (abs(_CompositingParams.x) < _GizmoCircleRadius.y / _GetBlendFactorMaxGizmoDistance) |
|||
{ |
|||
if (distanceFromCenter < _BlendFactorCircleRadius) |
|||
{ |
|||
float alpha = smoothstep(1.0, 0.6, distanceFromCenter / _BlendFactorCircleRadius) * 0.75; |
|||
result = lerp(result, float4(1.0, 1.0, 1.0, alpha), alpha); |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
float GetZoneViewFeedbackCircleFactor(float2 normalizedCoord, float radius, float circleSize) |
|||
{ |
|||
float distanceToCenter = abs(length(_GizmoZoneCenter.xy - normalizedCoord) - radius); |
|||
return saturate((circleSize - distanceToCenter) / circleSize); |
|||
} |
|||
|
|||
float ComputeBorderFactor(float borderSize, float2 screenPos, bool sideBySideView) |
|||
{ |
|||
float4 borderSize4 = float4(borderSize, borderSize, borderSize, borderSize); |
|||
float4 distanceToBorder = float4(screenPos.x, screenPos.y, abs(_ScreenRatio.z - screenPos.x), abs(_ScreenRatio.w - screenPos.y)); |
|||
|
|||
float4 factors = saturate((borderSize4 - distanceToBorder) / borderSize4); // Lerp from 1.0 to 0.0 alpha from screen border to border size |
|||
float factor = max(factors.x, max(factors.y, max(factors.z, factors.w))); |
|||
|
|||
// Add middle of the screen for side by side view |
|||
if (sideBySideView) |
|||
{ |
|||
float distanceToCenterLine = abs(_ScreenRatio.z * 0.5 - screenPos.x); |
|||
float factorForCenterLine = saturate((borderSize - distanceToCenterLine) / borderSize); |
|||
factor = max(factor, factorForCenterLine); |
|||
} |
|||
|
|||
return factor; |
|||
} |
|||
|
|||
float ComputeSelectedSideColorFactor(float side, float2 screenPos, float2 normalizedCoord, bool sideBySideView, bool zoneView) |
|||
{ |
|||
float borderSize = 2.0; |
|||
bool selectedSide = side * _CompositingParams.w > 0.0; |
|||
|
|||
float factor = ComputeBorderFactor(borderSize, screenPos, sideBySideView); |
|||
|
|||
// Add circle for zone view |
|||
if (zoneView) |
|||
{ |
|||
float selectionCircleFeedbackFactor = GetZoneViewFeedbackCircleFactor(normalizedCoord, _GizmoCircleRadius.y, 0.002); |
|||
factor = max(factor, selectionCircleFeedbackFactor); |
|||
} |
|||
|
|||
// If not on the selected side, make it more transparent |
|||
if (!selectedSide) |
|||
{ |
|||
factor = factor * 0.2; |
|||
} |
|||
|
|||
return factor; |
|||
} |
|||
|
|||
float4 ComputeDragColorFactor(float side, float2 screenPos, float2 normalizedCoord, bool sideBySideView, bool zoneView) |
|||
{ |
|||
float factor = 0; |
|||
float borderSize = 40.0; |
|||
bool sideIsDragZone = (side > 0.0 && _CompositingParams2.x > 0.0) || (side < 0.0 && _CompositingParams2.x < 0.0); |
|||
if (sideIsDragZone) |
|||
{ |
|||
factor = ComputeBorderFactor(borderSize, screenPos, sideBySideView); |
|||
|
|||
// Add circle for zone view |
|||
if (zoneView && side < 0.0) |
|||
{ |
|||
float feedbackRadius = _GizmoLength * 2.0 * 0.3; // make it proprtional to selection zone |
|||
factor = max(factor, GetZoneViewFeedbackCircleFactor(normalizedCoord, _GizmoLength * 2.0, feedbackRadius)); |
|||
} |
|||
|
|||
factor = pow(factor, 8) * 0.7; // Casimir magics values for optimum fadeout :) |
|||
} |
|||
|
|||
return factor; |
|||
} |
|||
|
|||
float4 ComputeFeedbackColor(float4 inputColor, float side, float2 screenPos, float2 normalizedCoord, bool sideBySideView, bool zoneView) |
|||
{ |
|||
float factor = ComputeSelectedSideColorFactor(side, screenPos, normalizedCoord, sideBySideView, zoneView); |
|||
factor = max(factor, ComputeDragColorFactor(side, screenPos, normalizedCoord, sideBySideView, zoneView)); |
|||
|
|||
float4 result = float4(0.0, 0.0, 0.0, 0.0); |
|||
if (side > 0.0) |
|||
{ |
|||
result = lerp(inputColor, _FirstViewColor, factor); |
|||
} |
|||
else |
|||
{ |
|||
result = lerp(inputColor, _SecondViewColor, factor); |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
|
|||
v2f vert(appdata_t IN) |
|||
{ |
|||
v2f OUT; |
|||
OUT.vertex = UnityObjectToClipPos(IN.vertex); |
|||
OUT.texcoord = TRANSFORM_TEX(IN.texcoord, _Tex0WithSun); |
|||
return OUT; |
|||
} |
|||
|
|||
float3 evalCurve(float3 x, float A, float B, float C, float D, float E, float F) |
|||
{ |
|||
return ((x*(A*x + C*B) + D*E) / (x*(A*x + B) + D*F)) - E / F; |
|||
} |
|||
|
|||
float3 applyTonemapFilmicAD(float3 linearColor) |
|||
{ |
|||
float blackRatio = InBlack / OutBlack; |
|||
float whiteRatio = InWhite / OutWhite; |
|||
|
|||
// blend tunable coefficients |
|||
float B = lerp(0.57, 0.37, blackRatio); |
|||
float C = lerp(0.01, 0.24, whiteRatio); |
|||
float D = lerp(0.02, 0.20, blackRatio); |
|||
|
|||
// constants |
|||
float A = 0.2; |
|||
float E = 0.02; |
|||
float F = 0.30; |
|||
|
|||
// eval and correct for white point |
|||
float3 whiteScale = 1.0f / evalCurve(WhiteLevel, A, B, C, D, E, F); |
|||
float3 curr = evalCurve(linearColor *whiteScale, A, B, C, D, E, F); |
|||
|
|||
return curr*whiteScale; |
|||
} |
|||
|
|||
float3 remapWhite(float3 inPixel, float whitePt) |
|||
{ |
|||
// var breakout for readability |
|||
const float inBlack = 0; |
|||
const float outBlack = 0; |
|||
float inWhite = whitePt; |
|||
const float outWhite = 1; |
|||
|
|||
// remap input range to output range |
|||
float3 outPixel = ((inPixel.rgb) - inBlack.xxx) / (inWhite.xxx - inBlack.xxx) * (outWhite.xxx - outBlack.xxx) + outBlack.xxx; |
|||
return (outPixel.rgb); |
|||
} |
|||
|
|||
float3 NeutralTonemap(float3 x) |
|||
{ |
|||
float3 finalColor = applyTonemapFilmicAD(x); // curve (dynamic coeffs differ per level) |
|||
finalColor = remapWhite(finalColor, WhiteClip); // post-curve white point adjustment |
|||
finalColor = saturate(finalColor); |
|||
return finalColor; |
|||
} |
|||
|
|||
float3 ApplyToneMap(float3 color) |
|||
{ |
|||
if (_CompositingParams2.y > 0.0) |
|||
{ |
|||
return NeutralTonemap(color); |
|||
} |
|||
else |
|||
{ |
|||
return saturate(color); |
|||
} |
|||
} |
|||
|
|||
float3 ComputeColor(sampler2D texNormal, sampler2D texWithoutSun, sampler2D texShadowMask, float shadowMultiplier, float4 shadowColor, float2 texcoord) |
|||
{ |
|||
// Explanation of how this work: |
|||
// To simulate the shadow of a directional light, we want to interpolate between two environments. One with a skybox without sun for shadowed area and the other with the sun. |
|||
// To create the lerp mask we render the scene with a white diffuse material and a single shadow casting directional light. |
|||
// This will create a mask where the shadowed area is 0 and the lit area is 1 with a smooth NDotL transition in-between. |
|||
// However, the DNotL will create an unwanted darkening of the scene (it's not actually part of the lighting equation) |
|||
// so we sqrt it in order to avoid too much darkening. |
|||
float3 color = tex2D(texNormal, texcoord).rgb; |
|||
float3 colorWithoutsun = tex2D(texWithoutSun, texcoord).rgb; |
|||
float3 shadowMask = sqrt(tex2D(texShadowMask, texcoord).rgb); |
|||
return lerp(colorWithoutsun * shadowColor.rgb * shadowMultiplier, color, saturate(shadowMask.r)); |
|||
} |
|||
|
|||
ENDCG |
|||
|
|||
SubShader |
|||
{ |
|||
Tags |
|||
{ |
|||
"ForceSupported" = "True" |
|||
} |
|||
|
|||
Lighting Off |
|||
Cull Off |
|||
ZTest Always |
|||
ZWrite Off |
|||
Blend One Zero |
|||
|
|||
// Single view 1 |
|||
Pass |
|||
{ |
|||
CGPROGRAM |
|||
#pragma fragment frag |
|||
#pragma target 3.0 |
|||
|
|||
float4 frag(float2 texcoord : TEXCOORD0, |
|||
UNITY_VPOS_TYPE vpos : VPOS) : COLOR |
|||
{ |
|||
float4 color = float4(ComputeColor(_Tex0WithSun, _Tex0WithoutSun, _Tex0Shadows, ShadowMultiplier0, _ShadowColor0, texcoord) * exp2(ExposureValue1), 1.0); |
|||
color.rgb = ApplyToneMap(color.rgb); |
|||
color = ComputeFeedbackColor(color, 1.0, vpos.xy, float2(0.0, 0.0), false, false); |
|||
return color; |
|||
} |
|||
ENDCG |
|||
} |
|||
|
|||
// Single view 2 |
|||
Pass |
|||
{ |
|||
CGPROGRAM |
|||
#pragma fragment frag |
|||
#pragma target 3.0 |
|||
|
|||
float4 frag(float2 texcoord : TEXCOORD0, |
|||
UNITY_VPOS_TYPE vpos : VPOS) : COLOR |
|||
{ |
|||
float4 color = float4(ComputeColor(_Tex1WithSun, _Tex1WithoutSun, _Tex1Shadows, ShadowMultiplier1, _ShadowColor1, texcoord) * exp2(ExposureValue2), 1.0); |
|||
color.rgb = ApplyToneMap(color.rgb); |
|||
color = ComputeFeedbackColor(color, -1.0, vpos.xy, float2(0.0, 0.0), false, false); |
|||
return color; |
|||
} |
|||
ENDCG |
|||
} |
|||
|
|||
// split |
|||
Pass |
|||
{ |
|||
CGPROGRAM |
|||
#pragma fragment frag |
|||
#pragma target 3.0 |
|||
|
|||
float4 frag(float2 texcoord : TEXCOORD0, |
|||
UNITY_VPOS_TYPE vpos : VPOS) : COLOR |
|||
{ |
|||
float3 color1 = ComputeColor(_Tex0WithSun, _Tex0WithoutSun, _Tex0Shadows, ShadowMultiplier0, _ShadowColor0, texcoord) * exp2(ExposureValue1); |
|||
float3 color2 = ComputeColor(_Tex1WithSun, _Tex1WithoutSun, _Tex1Shadows, ShadowMultiplier1, _ShadowColor1, texcoord) * exp2(ExposureValue2); |
|||
|
|||
float2 normalizedCoord = ((texcoord * 2.0 - 1.0) * _ScreenRatio.xy); |
|||
|
|||
float side = DistanceToSplit(normalizedCoord, _GizmoSplitPlane) < 0.0f ? -1.0f : 1.0f; |
|||
float blendFactor = 0.0f; |
|||
if (side < 0.0) |
|||
{ |
|||
blendFactor = 1.0 - saturate(side * _CompositingParams.x); |
|||
} |
|||
else |
|||
{ |
|||
blendFactor = saturate(side * _CompositingParams.x); |
|||
} |
|||
|
|||
float4 finalColor = float4(lerp(color1, color2, blendFactor), 1.0); |
|||
finalColor.rgb = ApplyToneMap(finalColor.rgb); |
|||
|
|||
float4 gizmoColor = GetGizmoColor(normalizedCoord, _GizmoSplitPlane, _GizmoSplitPlaneOrtho); |
|||
finalColor = lerp(finalColor, gizmoColor, gizmoColor.a); |
|||
finalColor = ComputeFeedbackColor(finalColor, side, vpos.xy, float2(0.0, 0.0), false, false); |
|||
|
|||
return finalColor; |
|||
} |
|||
ENDCG |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 4386e57b23a56004c93e1d57d2bbcb4f |
|||
ShaderImporter: |
|||
externalObjects: {} |
|||
defaultTextures: [] |
|||
nonModifiableTextures: [] |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEngine; |
|||
|
|||
namespace UnityEditor.Rendering.LookDev |
|||
{ |
|||
/// <summary>
|
|||
/// Different working views in LookDev
|
|||
/// </summary>
|
|||
public enum ViewIndex |
|||
{ |
|||
First, |
|||
Second |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Same as <see cref="ViewIndex"/> plus a compound value
|
|||
/// </summary>
|
|||
public enum ViewCompositionIndex |
|||
{ |
|||
First = ViewIndex.First, |
|||
Second = ViewIndex.Second, |
|||
Composite |
|||
}; |
|||
|
|||
// /!\ WARNING: these value name are used as uss file too.
|
|||
// if your rename here, rename in the uss too.
|
|||
/// <summary>
|
|||
/// Different layout supported in LookDev
|
|||
/// </summary>
|
|||
public enum Layout |
|||
{ |
|||
FullFirstView, |
|||
FullSecondView, |
|||
HorizontalSplit, |
|||
VerticalSplit, |
|||
CustomSplit |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Statis of the side panel of the LookDev window
|
|||
/// </summary>
|
|||
public enum SidePanel |
|||
{ |
|||
None = -1, |
|||
Environment, |
|||
Debug |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Class containing all data used by the LookDev Window to render
|
|||
/// </summary>
|
|||
[System.Serializable] |
|||
public class Context : ScriptableObject, IDisposable |
|||
{ |
|||
[SerializeField] |
|||
string m_EnvironmentLibraryGUID = ""; //Empty GUID
|
|||
|
|||
[SerializeField] |
|||
bool m_CameraSynced = true; |
|||
|
|||
/// <summary>The currently used Environment</summary>
|
|||
public EnvironmentLibrary environmentLibrary { get; private set; } |
|||
|
|||
/// <summary>The currently used layout</summary>
|
|||
[field: SerializeField] |
|||
public LayoutContext layout { get; private set; } = new LayoutContext(); |
|||
|
|||
/// <summary>
|
|||
/// State if both views camera movement are synced or not
|
|||
/// </summary>
|
|||
public bool cameraSynced |
|||
{ |
|||
get => m_CameraSynced; |
|||
set |
|||
{ |
|||
if (m_CameraSynced ^ value) |
|||
{ |
|||
if (value) |
|||
EditorApplication.update += SynchronizeCameraStates; |
|||
else |
|||
EditorApplication.update -= SynchronizeCameraStates; |
|||
m_CameraSynced = value; |
|||
} |
|||
} |
|||
} |
|||
|
|||
[SerializeField] |
|||
ViewContext[] m_Views = new ViewContext[2] |
|||
{ |
|||
new ViewContext(), |
|||
new ViewContext() |
|||
}; |
|||
|
|||
/// <summary>
|
|||
/// Get datas relative to a view
|
|||
/// </summary>
|
|||
/// <param name="index">The view index to look at</param>
|
|||
/// <returns>Datas for the selected view</returns>
|
|||
public ViewContext GetViewContent(ViewIndex index) |
|||
=> m_Views[(int)index]; |
|||
|
|||
internal void Init() |
|||
{ |
|||
LoadEnvironmentLibraryFromGUID(); |
|||
|
|||
//recompute non serialized computes states
|
|||
layout.gizmoState.Init(); |
|||
|
|||
if (cameraSynced) |
|||
EditorApplication.update += SynchronizeCameraStates; |
|||
} |
|||
|
|||
/// <summary>Update the environment used.</summary>
|
|||
/// <param name="environmentOrCubemapAsset">
|
|||
/// The new <see cref="Environment"/> to use.
|
|||
/// Or the <see cref="Cubemap"/> to use to build a new one.
|
|||
/// Other types will raise an ArgumentException.
|
|||
/// </param>
|
|||
public void UpdateEnvironmentLibrary(EnvironmentLibrary library) |
|||
{ |
|||
m_EnvironmentLibraryGUID = ""; |
|||
environmentLibrary = null; |
|||
if (library == null || library.Equals(null)) |
|||
return; |
|||
|
|||
m_EnvironmentLibraryGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(library)); |
|||
environmentLibrary = library; |
|||
} |
|||
|
|||
void LoadEnvironmentLibraryFromGUID() |
|||
{ |
|||
environmentLibrary = null; |
|||
|
|||
GUID storedGUID; |
|||
GUID.TryParse(m_EnvironmentLibraryGUID, out storedGUID); |
|||
if (storedGUID.Empty()) |
|||
return; |
|||
|
|||
string path = AssetDatabase.GUIDToAssetPath(m_EnvironmentLibraryGUID); |
|||
environmentLibrary = AssetDatabase.LoadAssetAtPath<EnvironmentLibrary>(path); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Synchronize cameras from both view using data from the baseCameraState
|
|||
/// </summary>
|
|||
/// <param name="baseCameraState">The <see cref="ViewIndex"/> to be used as reference</param>
|
|||
public void SynchronizeCameraStates(ViewIndex baseCameraState) |
|||
{ |
|||
switch (baseCameraState) |
|||
{ |
|||
case ViewIndex.First: |
|||
m_Views[1].camera.SynchronizeFrom(m_Views[0].camera); |
|||
break; |
|||
case ViewIndex.Second: |
|||
m_Views[0].camera.SynchronizeFrom(m_Views[1].camera); |
|||
break; |
|||
default: |
|||
throw new System.ArgumentException("Unknow ViewIndex given in parameter."); |
|||
} |
|||
} |
|||
|
|||
void SynchronizeCameraStates() |
|||
=> SynchronizeCameraStates(layout.lastFocusedView); |
|||
|
|||
/// <summary>
|
|||
/// Change focused view.
|
|||
/// Focused view is the base view to copy data when syncing views' cameras
|
|||
/// </summary>
|
|||
/// <param name="index">The index of the view</param>
|
|||
public void SetFocusedCamera(ViewIndex index) |
|||
=> layout.lastFocusedView = index; |
|||
|
|||
|
|||
private bool disposedValue = false; // To detect redundant calls
|
|||
void IDisposable.Dispose() |
|||
{ |
|||
if (!disposedValue) |
|||
{ |
|||
if (cameraSynced) |
|||
EditorApplication.update -= SynchronizeCameraStates; |
|||
|
|||
disposedValue = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Data regarding the layout currently used in LookDev
|
|||
/// </summary>
|
|||
[System.Serializable] |
|||
public class LayoutContext |
|||
{ |
|||
/// <summary>The layout used</summary>
|
|||
public Layout viewLayout; |
|||
/// <summary>The last focused view</summary>
|
|||
public ViewIndex lastFocusedView = ViewIndex.First; |
|||
/// <summary>The state of the side panel</summary>
|
|||
public SidePanel showedSidePanel; |
|||
|
|||
[SerializeField] |
|||
internal ComparisonGizmoState gizmoState = new ComparisonGizmoState(); |
|||
|
|||
internal bool isSimpleView => viewLayout == Layout.FullFirstView || viewLayout == Layout.FullSecondView; |
|||
internal bool isMultiView => viewLayout == Layout.HorizontalSplit || viewLayout == Layout.VerticalSplit; |
|||
internal bool isCombinedView => viewLayout == Layout.CustomSplit; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Data container containing content of a view
|
|||
/// </summary>
|
|||
[System.Serializable] |
|||
public class ViewContext |
|||
{ |
|||
/// <summary>The position and rotation of the camera</summary>
|
|||
[field: SerializeField] |
|||
public CameraState camera { get; private set; } = new CameraState(); |
|||
|
|||
/// <summary>The currently viewed debugState</summary>
|
|||
[field: SerializeField] |
|||
public DebugContext debug { get; private set; } = new DebugContext(); |
|||
|
|||
//Environment asset, sub-asset (under a library) or cubemap
|
|||
[SerializeField] |
|||
string environmentGUID = ""; //Empty GUID
|
|||
|
|||
/// <summary>
|
|||
/// Check if an Environment is registered for this view.
|
|||
/// The result will be accurate even if the Environment have not been reloaded yet.
|
|||
/// </summary>
|
|||
public bool hasEnvironment => !String.IsNullOrEmpty(environmentGUID); |
|||
|
|||
/// <summary>The currently used Environment</summary>
|
|||
public Environment environment { get; private set; } |
|||
|
|||
[SerializeField] |
|||
string viewedObjectAssetGUID = ""; //Empty GUID
|
|||
|
|||
// Careful here: we want to keep it while reloading script.
|
|||
// But from one unity editor to an other, ID are not kept.
|
|||
// So, only use it when reloading from script update.
|
|||
[SerializeField] |
|||
int viewedObjecHierarchytInstanceID; |
|||
|
|||
/// <summary>
|
|||
/// Check if an Environment is registered for this view.
|
|||
/// The result will be accurate even if the object have not been reloaded yet.
|
|||
/// </summary>
|
|||
public bool hasViewedObject => |
|||
!String.IsNullOrEmpty(viewedObjectAssetGUID) |
|||
|| viewedObjecHierarchytInstanceID != 0; |
|||
|
|||
/// <summary>Reference to the object given for instantiation.</summary>
|
|||
public GameObject viewedObjectReference { get; private set; } |
|||
|
|||
/// <summary>
|
|||
/// The currently displayed instance of <see cref="viewedObjectReference"/>.
|
|||
/// It will be instantiated when pushing changes to renderer.
|
|||
/// See <see cref="LookDev.SaveContextChangeAndApply(ViewIndex)"/>
|
|||
/// </summary>
|
|||
public GameObject viewedInstanceInPreview { get; internal set; } |
|||
|
|||
/// <summary>Update the environment used.</summary>
|
|||
/// <param name="environmentOrCubemapAsset">
|
|||
/// The new <see cref="Environment"/> to use.
|
|||
/// Or the <see cref="Cubemap"/> to use to build a new one.
|
|||
/// Other types will raise an ArgumentException.
|
|||
/// </param>
|
|||
public void UpdateEnvironment(UnityEngine.Object environmentOrCubemapAsset) |
|||
{ |
|||
environmentGUID = ""; |
|||
environment = null; |
|||
if (environmentOrCubemapAsset == null || environmentOrCubemapAsset.Equals(null)) |
|||
return; |
|||
|
|||
if (!(environmentOrCubemapAsset is Environment) |
|||
&& !(environmentOrCubemapAsset is Cubemap)) |
|||
throw new System.ArgumentException("Only Environment or Cubemap accepted for environmentOrCubemapAsset parameter"); |
|||
|
|||
string GUID; |
|||
long localIDInFile; |
|||
AssetDatabase.TryGetGUIDAndLocalFileIdentifier(environmentOrCubemapAsset, out GUID, out localIDInFile); |
|||
environmentGUID = $"{GUID},{localIDInFile}"; |
|||
|
|||
if (environmentOrCubemapAsset is Environment) |
|||
environment = environmentOrCubemapAsset as Environment; |
|||
else //Cubemap
|
|||
{ |
|||
environment = new Environment(); |
|||
environment.sky.cubemap = environmentOrCubemapAsset as Cubemap; |
|||
} |
|||
} |
|||
|
|||
void LoadEnvironmentFromGUID() |
|||
{ |
|||
environment = null; |
|||
|
|||
GUID storedGUID; |
|||
string[] GUIDAndLocalIDInFile = environmentGUID.Split(new[] { ',' }); |
|||
GUID.TryParse(GUIDAndLocalIDInFile[0], out storedGUID); |
|||
if (storedGUID.Empty()) |
|||
return; |
|||
long localIDInFile = GUIDAndLocalIDInFile.Length < 2 ? 0L : long.Parse(GUIDAndLocalIDInFile[1]); |
|||
|
|||
string path = AssetDatabase.GUIDToAssetPath(GUIDAndLocalIDInFile[0]); |
|||
|
|||
Type savedType = AssetDatabase.GetMainAssetTypeAtPath(path); |
|||
if (savedType == typeof(EnvironmentLibrary)) |
|||
{ |
|||
object[] loaded = AssetDatabase.LoadAllAssetsAtPath(path); |
|||
for (int i = 0; i < loaded.Length; ++i) |
|||
{ |
|||
string garbage; |
|||
long testedLocalIndex; |
|||
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier((UnityEngine.Object)loaded[i], out garbage, out testedLocalIndex) |
|||
&& testedLocalIndex == localIDInFile) |
|||
{ |
|||
environment = loaded[i] as Environment; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
else if (savedType == typeof(Environment)) |
|||
environment = AssetDatabase.LoadAssetAtPath<Environment>(path); |
|||
else if (savedType == typeof(Cubemap)) |
|||
{ |
|||
Cubemap cubemap = AssetDatabase.LoadAssetAtPath<Cubemap>(path); |
|||
environment = new Environment(); |
|||
environment.sky.cubemap = cubemap; |
|||
} |
|||
} |
|||
|
|||
/// <summary>Update the object reference used for instantiation.</summary>
|
|||
/// <param name="viewedObject">The new reference.</param>
|
|||
public void UpdateViewedObject(GameObject viewedObject) |
|||
{ |
|||
viewedObjectAssetGUID = ""; |
|||
viewedObjecHierarchytInstanceID = 0; |
|||
viewedObjectReference = null; |
|||
if (viewedObject == null || viewedObject.Equals(null)) |
|||
return; |
|||
|
|||
bool fromHierarchy = viewedObject.scene.IsValid(); |
|||
if (fromHierarchy) |
|||
viewedObjecHierarchytInstanceID = viewedObject.GetInstanceID(); |
|||
else |
|||
viewedObjectAssetGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(viewedObject)); |
|||
viewedObjectReference = viewedObject; |
|||
} |
|||
|
|||
//WARNING: only for script reloading
|
|||
void LoadViewedObject() |
|||
{ |
|||
viewedObjectReference = null; |
|||
|
|||
GUID storedGUID; |
|||
GUID.TryParse(viewedObjectAssetGUID, out storedGUID); |
|||
if (!storedGUID.Empty()) |
|||
{ |
|||
string path = AssetDatabase.GUIDToAssetPath(viewedObjectAssetGUID); |
|||
viewedObjectReference = AssetDatabase.LoadAssetAtPath<GameObject>(path); |
|||
} |
|||
else if (viewedObjecHierarchytInstanceID != 0) |
|||
{ |
|||
viewedObjectReference = EditorUtility.InstanceIDToObject(viewedObjecHierarchytInstanceID) as GameObject; |
|||
} |
|||
} |
|||
|
|||
internal void LoadAll(bool reloadWithTemporaryID) |
|||
{ |
|||
if (!reloadWithTemporaryID) |
|||
CleanTemporaryObjectIndexes(); |
|||
LoadEnvironmentFromGUID(); |
|||
LoadViewedObject(); |
|||
} |
|||
|
|||
internal void CleanTemporaryObjectIndexes() |
|||
=> viewedObjecHierarchytInstanceID = 0; |
|||
|
|||
/// <summary>Reset the camera state to default values</summary>
|
|||
public void ResetCameraState() |
|||
=> camera.Reset(); |
|||
} |
|||
|
|||
|
|||
/// <summary>
|
|||
/// Class that will contain debug value used.
|
|||
/// </summary>
|
|||
[System.Serializable] |
|||
public class DebugContext |
|||
{ |
|||
///// <summary>Display the debug grey balls</summary>
|
|||
//public bool greyBalls;
|
|||
|
|||
//[SerializeField]
|
|||
//string colorChartGUID = ""; //Empty GUID
|
|||
|
|||
///// <summary>The currently used color chart</summary>
|
|||
//public Texture2D colorChart { get; private set; }
|
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: e6c442cd75783b64283e30cff5a7d377 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
Shader "Hidden/LookDev/CubeToLatlong" |
|||
{ |
|||
Properties |
|||
{ |
|||
[NoScaleOffset] _MainTex ("Cubemap", Any) = "grey" {} |
|||
_CubeToLatLongParams ("Parameters", Vector) = (0.0, 0.0, 0.0, 0.0) |
|||
_WindowParams("Window params", Vector) = (0.0, 0.0, 0.0, 0.0) |
|||
} |
|||
|
|||
CGINCLUDE |
|||
|
|||
#include "UnityCG.cginc" |
|||
|
|||
uniform float4 _MainTex_HDR; |
|||
uniform float4 _MainTex_ST; |
|||
UNITY_DECLARE_TEXCUBE(_MainTex); |
|||
uniform float4 _CubeToLatLongParams; // x angle offset, y alpha, z intensity w lod to use |
|||
uniform float4 _WindowParams; // x Editor windows height, y Environment windows posY, z margin (constant of 2), w PixelsPerPoint |
|||
uniform bool _ManualTex2SRGB; |
|||
|
|||
#define OutputAlpha _CubeToLatLongParams.y |
|||
#define Intensity _CubeToLatLongParams.z |
|||
#define CurrentLOD _CubeToLatLongParams.w |
|||
|
|||
struct appdata_t |
|||
{ |
|||
float4 vertex : POSITION; |
|||
float2 texcoord : TEXCOORD0; |
|||
}; |
|||
|
|||
struct v2f |
|||
{ |
|||
float2 texcoord : TEXCOORD0; |
|||
float4 vertex : SV_POSITION; |
|||
}; |
|||
|
|||
v2f vert(appdata_t IN) |
|||
{ |
|||
v2f OUT; |
|||
OUT.vertex = UnityObjectToClipPos(IN.vertex); |
|||
OUT.texcoord = TRANSFORM_TEX(IN.texcoord, _MainTex); |
|||
|
|||
return OUT; |
|||
} |
|||
|
|||
float4 frag( float2 texcoord : TEXCOORD0, |
|||
UNITY_VPOS_TYPE vpos : VPOS |
|||
) : COLOR |
|||
{ |
|||
float2 texCoord = texcoord.xy; |
|||
float theta = texCoord.y * UNITY_PI; |
|||
float phi = (texCoord.x * 2.f * UNITY_PI - UNITY_PI*0.5f) - _CubeToLatLongParams.x; |
|||
|
|||
float cosTheta = cos(theta); |
|||
float sinTheta = sqrt(1.0f - min(1.0f, cosTheta*cosTheta)); |
|||
float cosPhi = cos(phi); |
|||
float sinPhi = sin(phi); |
|||
|
|||
float3 direction = float3(sinTheta*cosPhi, cosTheta, sinTheta*sinPhi); |
|||
direction.xy *= -1.0; |
|||
float4 ret = float4(DecodeHDR(UNITY_SAMPLE_TEXCUBE_LOD(_MainTex, direction, CurrentLOD), _MainTex_HDR) * Intensity, OutputAlpha); |
|||
if (_ManualTex2SRGB) |
|||
ret.rgb = LinearToGammaSpace(ret.rgb); |
|||
|
|||
// Clip outside of the library window |
|||
|
|||
// Editor windows is like this: |
|||
//------ |
|||
// Margin (2) |
|||
// Scene - Game - Asset Store <= What we call tab size |
|||
//------ |
|||
// Settings - Views <= what we call menu size |
|||
//---- |
|||
// View size with Environment windows) |
|||
// |
|||
// _WindowParams.x contain the height of the editor windows |
|||
// _WindowParams.y contain the start of the windows environment in the windows editor, i.e the menu size + tab size |
|||
// _WindowParams.z contain a constant margin of 2 (don't know how to retrieve that) |
|||
// _WindowParams.w is PixelsPerPoin (To handle retina display on OSX)) |
|||
|
|||
// We use VPOS register to clip, VPOS is dependent on the API. It is reversed in openGL. |
|||
// There is no need to clip when y is above height because the editor windows will clip it |
|||
// vertex.y is relative to editor windows |
|||
#if UNITY_UV_STARTS_AT_TOP |
|||
if ((vpos.y / _WindowParams.w) < (_WindowParams.y + _WindowParams.z)) |
|||
#else |
|||
// vertex.y is reversed (start from bottom of the editor windsows) |
|||
vpos.y = _WindowParams.x - (vpos.y / _WindowParams.w); |
|||
if (vpos.y < _WindowParams.z) |
|||
#endif |
|||
{ |
|||
clip(-1); |
|||
} |
|||
return ret; |
|||
} |
|||
ENDCG |
|||
|
|||
SubShader |
|||
{ |
|||
Tags |
|||
{ |
|||
"ForceSupported"="True" |
|||
} |
|||
|
|||
Lighting Off |
|||
Cull Off |
|||
ZTest Always |
|||
ZWrite Off |
|||
|
|||
Pass |
|||
{ |
|||
Blend One Zero |
|||
|
|||
CGPROGRAM |
|||
#pragma fragment frag |
|||
#pragma vertex vert |
|||
#pragma target 3.0 |
|||
ENDCG |
|||
} |
|||
|
|||
Pass |
|||
{ |
|||
Blend SrcAlpha OneMinusSrcAlpha |
|||
|
|||
CGPROGRAM |
|||
#pragma fragment frag |
|||
#pragma vertex vert |
|||
#pragma target 3.0 |
|||
ENDCG |
|||
} |
|||
|
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: a16365e8c873daa4c94919438490b905 |
|||
ShaderImporter: |
|||
externalObjects: {} |
|||
defaultTextures: [] |
|||
nonModifiableTextures: [] |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
|
|||
/* ******************************* */ |
|||
/* override for personal skin them */ |
|||
/* ******************************* */ |
|||
#environmentContainer > EnvironmentElement |
|||
{ |
|||
border-color: #999999; |
|||
} |
|||
|
|||
#separator |
|||
{ |
|||
border-color: #999999; |
|||
} |
|||
|
|||
.list-environment-overlay > ToolbarButton |
|||
{ |
|||
background-color: #CBCBCB; |
|||
} |
|||
|
|||
#inspector-header |
|||
{ |
|||
background-color: #CBCBCB; |
|||
border-color: #999999; |
|||
} |
|||
|
|||
#separator-line |
|||
{ |
|||
background-color: #CBCBCB; |
|||
} |
|||
|
|||
Image.unity-list-view__item:selected |
|||
{ |
|||
border-color: #3A72B0; |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 7c95237923780ad4683f959f53629b60 |
|||
ScriptedImporter: |
|||
internalIDToNameTable: [] |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} |
|||
disableValidation: 0 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using UnityEditor.UIElements; |
|||
using UnityEngine; |
|||
using UnityEngine.UIElements; |
|||
|
|||
namespace UnityEditor.Rendering.LookDev |
|||
{ |
|||
/// <summary>Interface that must implement the viewer to communicate with the compositor and data management</summary>
|
|||
public interface IViewDisplayer |
|||
{ |
|||
Rect GetRect(ViewCompositionIndex index); |
|||
void SetTexture(ViewCompositionIndex index, Texture texture); |
|||
|
|||
void Repaint(); |
|||
|
|||
event Action<Layout, SidePanel> OnLayoutChanged; |
|||
|
|||
event Action OnRenderDocAcquisitionTriggered; |
|||
|
|||
event Action<IMouseEvent> OnMouseEventInView; |
|||
|
|||
event Action<GameObject, ViewCompositionIndex, Vector2> OnChangingObjectInView; |
|||
event Action<UnityEngine.Object, ViewCompositionIndex, Vector2> OnChangingEnvironmentInView; |
|||
|
|||
event Action OnClosed; |
|||
|
|||
event Action OnUpdateRequested; |
|||
} |
|||
|
|||
/// <summary>Interface that must implement the EnvironmentLibrary view to communicate with the data management</summary>
|
|||
public interface IEnvironmentDisplayer |
|||
{ |
|||
void Repaint(); |
|||
|
|||
event Action<EnvironmentLibrary> OnChangingEnvironmentLibrary; |
|||
} |
|||
|
|||
class DisplayWindow : EditorWindow, IViewDisplayer, IEnvironmentDisplayer |
|||
{ |
|||
static class Style |
|||
{ |
|||
internal const string k_IconFolder = @"Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/"; |
|||
internal const string k_uss = @"Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow.uss"; |
|||
internal const string k_uss_personal_overload = @"Packages/com.unity.render-pipelines.core/Editor/LookDev/DisplayWindow-PersonalSkin.uss"; |
|||
|
|||
public static readonly GUIContent WindowTitleAndIcon = EditorGUIUtility.TrTextContentWithIcon("Look Dev", CoreEditorUtils.LoadIcon(k_IconFolder, "LookDevMainIcon", forceLowRes: true)); |
|||
} |
|||
|
|||
// /!\ WARNING:
|
|||
//The following const are used in the uss.
|
|||
//If you change them, update the uss file too.
|
|||
const string k_MainContainerName = "mainContainer"; |
|||
const string k_EnvironmentContainerName = "environmentContainer"; |
|||
const string k_ShowEnvironmentPanelClass = "showEnvironmentPanel"; |
|||
const string k_ViewContainerName = "viewContainer"; |
|||
const string k_FirstViewName = "firstView"; |
|||
const string k_SecondViewName = "secondView"; |
|||
const string k_ToolbarName = "toolbar"; |
|||
const string k_ToolbarRadioName = "toolbarRadio"; |
|||
const string k_TabsRadioName = "tabsRadio"; |
|||
const string k_SideToolbarName = "sideToolbar"; |
|||
const string k_SharedContainerClass = "container"; |
|||
const string k_FirstViewClass = "firstView"; |
|||
const string k_SecondViewsClass = "secondView"; |
|||
const string k_VerticalViewsClass = "verticalSplit"; |
|||
const string k_DebugContainerName = "debugContainer"; |
|||
const string k_ShowDebugPanelClass = "showDebugPanel"; |
|||
|
|||
VisualElement m_MainContainer; |
|||
VisualElement m_ViewContainer; |
|||
VisualElement m_DebugContainer; |
|||
PopupField<string> m_DebugView; |
|||
VisualElement m_EnvironmentContainer; |
|||
ListView m_EnvironmentList; |
|||
EnvironmentElement m_EnvironmentInspector; |
|||
Label m_NoEnvironmentList; |
|||
Label m_NoObject1; |
|||
Label m_NoEnvironment1; |
|||
Label m_NoObject2; |
|||
Label m_NoEnvironment2; |
|||
Toolbar m_EnvironmentListToolbar; |
|||
|
|||
Image[] m_Views = new Image[2]; |
|||
|
|||
Layout layout |
|||
{ |
|||
get => LookDev.currentContext.layout.viewLayout; |
|||
set |
|||
{ |
|||
if (LookDev.currentContext.layout.viewLayout != value) |
|||
{ |
|||
OnLayoutChangedInternal?.Invoke(value, sidePanel); |
|||
ApplyLayout(value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
SidePanel sidePanel |
|||
{ |
|||
get => LookDev.currentContext.layout.showedSidePanel; |
|||
set |
|||
{ |
|||
if (LookDev.currentContext.layout.showedSidePanel != value) |
|||
{ |
|||
OnLayoutChangedInternal?.Invoke(layout, value); |
|||
ApplySidePanelChange(value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
event Action<Layout, SidePanel> OnLayoutChangedInternal; |
|||
event Action<Layout, SidePanel> IViewDisplayer.OnLayoutChanged |
|||
{ |
|||
add => OnLayoutChangedInternal += value; |
|||
remove => OnLayoutChangedInternal -= value; |
|||
} |
|||
|
|||
event Action OnRenderDocAcquisitionTriggeredInternal; |
|||
event Action IViewDisplayer.OnRenderDocAcquisitionTriggered |
|||
{ |
|||
add => OnRenderDocAcquisitionTriggeredInternal += value; |
|||
remove => OnRenderDocAcquisitionTriggeredInternal -= value; |
|||
} |
|||
|
|||
event Action<IMouseEvent> OnMouseEventInViewPortInternal; |
|||
event Action<IMouseEvent> IViewDisplayer.OnMouseEventInView |
|||
{ |
|||
add => OnMouseEventInViewPortInternal += value; |
|||
remove => OnMouseEventInViewPortInternal -= value; |
|||
} |
|||
|
|||
event Action<GameObject, ViewCompositionIndex, Vector2> OnChangingObjectInViewInternal; |
|||
event Action<GameObject, ViewCompositionIndex, Vector2> IViewDisplayer.OnChangingObjectInView |
|||
{ |
|||
add => OnChangingObjectInViewInternal += value; |
|||
remove => OnChangingObjectInViewInternal -= value; |
|||
} |
|||
|
|||
//event Action<Material, ViewCompositionIndex, Vector2> OnChangingMaterialInViewInternal;
|
|||
//event Action<Material, ViewCompositionIndex, Vector2> IViewDisplayer.OnChangingMaterialInView
|
|||
//{
|
|||
// add => OnChangingMaterialInViewInternal += value;
|
|||
// remove => OnChangingMaterialInViewInternal -= value;
|
|||
//}
|
|||
|
|||
event Action<UnityEngine.Object, ViewCompositionIndex, Vector2> OnChangingEnvironmentInViewInternal; |
|||
event Action<UnityEngine.Object, ViewCompositionIndex, Vector2> IViewDisplayer.OnChangingEnvironmentInView |
|||
{ |
|||
add => OnChangingEnvironmentInViewInternal += value; |
|||
remove => OnChangingEnvironmentInViewInternal -= value; |
|||
} |
|||
|
|||
event Action OnClosedInternal; |
|||
event Action IViewDisplayer.OnClosed |
|||
{ |
|||
add => OnClosedInternal += value; |
|||
remove => OnClosedInternal -= value; |
|||
} |
|||
|
|||
//event Action<UnityEngine.Object> OnAddingEnvironmentInternal;
|
|||
//event Action<UnityEngine.Object> IEnvironmentDisplayer.OnAddingEnvironment
|
|||
//{
|
|||
// add => OnAddingEnvironmentInternal += value;
|
|||
// remove => OnAddingEnvironmentInternal -= value;
|
|||
//}
|
|||
|
|||
//event Action<int> OnRemovingEnvironmentInternal;
|
|||
//event Action<int> IEnvironmentDisplayer.OnRemovingEnvironment
|
|||
//{
|
|||
// add => OnRemovingEnvironmentInternal += value;
|
|||
// remove => OnRemovingEnvironmentInternal -= value;
|
|||
//}
|
|||
|
|||
event Action<EnvironmentLibrary> OnChangingEnvironmentLibraryInternal; |
|||
event Action<EnvironmentLibrary> IEnvironmentDisplayer.OnChangingEnvironmentLibrary |
|||
{ |
|||
add => OnChangingEnvironmentLibraryInternal += value; |
|||
remove => OnChangingEnvironmentLibraryInternal -= value; |
|||
} |
|||
|
|||
event Action OnUpdateRequestedInternal; |
|||
event Action IViewDisplayer.OnUpdateRequested |
|||
{ |
|||
add => OnUpdateRequestedInternal += value; |
|||
remove => OnUpdateRequestedInternal -= value; |
|||
} |
|||
|
|||
void OnEnable() |
|||
{ |
|||
//Call the open function to configure LookDev
|
|||
// in case the window where open when last editor session finished.
|
|||
// (Else it will open at start and has nothing to display).
|
|||
if (!LookDev.open) |
|||
LookDev.Open(); |
|||
|
|||
titleContent = Style.WindowTitleAndIcon; |
|||
|
|||
rootVisualElement.styleSheets.Add( |
|||
AssetDatabase.LoadAssetAtPath<StyleSheet>(Style.k_uss)); |
|||
|
|||
if (!EditorGUIUtility.isProSkin) |
|||
{ |
|||
rootVisualElement.styleSheets.Add( |
|||
AssetDatabase.LoadAssetAtPath<StyleSheet>(Style.k_uss_personal_overload)); |
|||
} |
|||
|
|||
CreateToolbar(); |
|||
|
|||
m_MainContainer = new VisualElement() { name = k_MainContainerName }; |
|||
m_MainContainer.AddToClassList(k_SharedContainerClass); |
|||
rootVisualElement.Add(m_MainContainer); |
|||
|
|||
CreateViews(); |
|||
CreateEnvironment(); |
|||
CreateDebug(); |
|||
CreateDropAreas(); |
|||
|
|||
ApplyLayout(layout); |
|||
ApplySidePanelChange(sidePanel); |
|||
} |
|||
|
|||
void OnDisable() => OnClosedInternal?.Invoke(); |
|||
|
|||
void CreateToolbar() |
|||
{ |
|||
// Layout swapper part
|
|||
var layoutRadio = new ToolbarRadio() { name = k_ToolbarRadioName }; |
|||
layoutRadio.AddRadios(new[] { |
|||
CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Layout1", forceLowRes: true), |
|||
CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Layout2", forceLowRes: true), |
|||
CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_LayoutVertical", forceLowRes: true), |
|||
CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_LayoutHorizontal", forceLowRes: true), |
|||
CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_LayoutCustom", forceLowRes: true) |
|||
}); |
|||
layoutRadio.RegisterCallback((ChangeEvent<int> evt) |
|||
=> layout = (Layout)evt.newValue); |
|||
layoutRadio.SetValueWithoutNotify((int)layout); |
|||
|
|||
var cameraMenu = new ToolbarMenu() { name = "cameraMenu" }; |
|||
cameraMenu.variant = ToolbarMenu.Variant.Popup; |
|||
var cameraToggle = new ToolbarToggle() { name = "cameraButton" }; |
|||
cameraToggle.value = LookDev.currentContext.cameraSynced; |
|||
|
|||
//Note: when having Image on top of the Toggle nested in the Menu, RegisterValueChangedCallback is not called
|
|||
//cameraToggle.RegisterValueChangedCallback(evt => LookDev.currentContext.cameraSynced = evt.newValue);
|
|||
cameraToggle.RegisterCallback<MouseUpEvent>(evt => |
|||
{ |
|||
LookDev.currentContext.cameraSynced ^= true; |
|||
cameraToggle.SetValueWithoutNotify(LookDev.currentContext.cameraSynced); |
|||
}); |
|||
|
|||
var cameraSeparator = new ToolbarToggle() { name = "cameraSeparator" }; |
|||
var texCamera1 = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Camera1", forceLowRes: true); |
|||
var texCamera2 = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Camera2", forceLowRes: true); |
|||
var texLink = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Link", forceLowRes: true); |
|||
var texRight = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Right", forceLowRes: true); |
|||
var texLeft = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_Left", forceLowRes: true); |
|||
cameraToggle.Add(new Image() { image = texCamera1 }); |
|||
cameraToggle.Add(new Image() { image = texLink }); |
|||
cameraToggle.Add(new Image() { image = texCamera2 }); |
|||
cameraMenu.Add(cameraToggle); |
|||
cameraMenu.Add(cameraSeparator); |
|||
cameraMenu.menu.AppendAction("Align Camera 1 with Camera 2", |
|||
(DropdownMenuAction a) => LookDev.currentContext.SynchronizeCameraStates(ViewIndex.Second), |
|||
DropdownMenuAction.AlwaysEnabled); |
|||
cameraMenu.menu.AppendAction("Align Camera 2 with Camera 1", |
|||
(DropdownMenuAction a) => LookDev.currentContext.SynchronizeCameraStates(ViewIndex.First), |
|||
DropdownMenuAction.AlwaysEnabled); |
|||
cameraMenu.menu.AppendAction("Reset Cameras", |
|||
(DropdownMenuAction a) => |
|||
{ |
|||
LookDev.currentContext.GetViewContent(ViewIndex.First).ResetCameraState(); |
|||
LookDev.currentContext.GetViewContent(ViewIndex.Second).ResetCameraState(); |
|||
}, |
|||
DropdownMenuAction.AlwaysEnabled); |
|||
|
|||
// Side part
|
|||
var sideRadio = new ToolbarRadio(canDeselectAll: true) { name = k_TabsRadioName }; |
|||
sideRadio.AddRadios(new[] { |
|||
"Environment", |
|||
"Debug" |
|||
}); |
|||
sideRadio.RegisterCallback((ChangeEvent<int> evt) => |
|||
{ |
|||
sidePanel = (SidePanel)evt.newValue; |
|||
if (sidePanel == SidePanel.Debug) |
|||
RefreshDebugViews(); |
|||
}); |
|||
sideRadio.SetValueWithoutNotify((int)sidePanel); |
|||
|
|||
var sideToolbar = new Toolbar() { name = k_SideToolbarName }; |
|||
sideToolbar.Add(sideRadio); |
|||
|
|||
// Aggregate parts
|
|||
var toolbar = new Toolbar() { name = k_ToolbarName }; |
|||
toolbar.Add(layoutRadio); |
|||
toolbar.Add(new ToolbarSpacer()); |
|||
toolbar.Add(cameraMenu); |
|||
|
|||
toolbar.Add(new ToolbarSpacer() { flex = true }); |
|||
if (UnityEditorInternal.RenderDoc.IsInstalled() && UnityEditorInternal.RenderDoc.IsLoaded()) |
|||
{ |
|||
var renderDocButton = new ToolbarButton(() => OnRenderDocAcquisitionTriggeredInternal?.Invoke()) |
|||
{ |
|||
name = "renderdoc-content" |
|||
}; |
|||
renderDocButton.Add(new Image() |
|||
{ |
|||
image = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "renderdoc", forceLowRes: true) |
|||
}); |
|||
renderDocButton.Add(new Label() { text = " Content" }); |
|||
toolbar.Add(renderDocButton); |
|||
} |
|||
toolbar.Add(sideToolbar); |
|||
rootVisualElement.Add(toolbar); |
|||
} |
|||
|
|||
void CreateViews() |
|||
{ |
|||
if (m_MainContainer == null || m_MainContainer.Equals(null)) |
|||
throw new System.MemberAccessException("m_MainContainer should be assigned prior CreateViews()"); |
|||
|
|||
m_ViewContainer = new VisualElement() { name = k_ViewContainerName }; |
|||
m_ViewContainer.AddToClassList(LookDev.currentContext.layout.isMultiView ? k_SecondViewsClass : k_FirstViewClass); |
|||
m_ViewContainer.AddToClassList(k_SharedContainerClass); |
|||
m_MainContainer.Add(m_ViewContainer); |
|||
m_ViewContainer.RegisterCallback<MouseDownEvent>(evt => OnMouseEventInViewPortInternal?.Invoke(evt)); |
|||
m_ViewContainer.RegisterCallback<MouseUpEvent>(evt => OnMouseEventInViewPortInternal?.Invoke(evt)); |
|||
m_ViewContainer.RegisterCallback<MouseMoveEvent>(evt => OnMouseEventInViewPortInternal?.Invoke(evt)); |
|||
|
|||
m_Views[(int)ViewIndex.First] = new Image() { name = k_FirstViewName, image = Texture2D.blackTexture }; |
|||
m_ViewContainer.Add(m_Views[(int)ViewIndex.First]); |
|||
m_Views[(int)ViewIndex.Second] = new Image() { name = k_SecondViewName, image = Texture2D.blackTexture }; |
|||
m_ViewContainer.Add(m_Views[(int)ViewIndex.Second]); |
|||
|
|||
var firstOrCompositeManipulator = new SwitchableCameraController( |
|||
LookDev.currentContext.GetViewContent(ViewIndex.First).camera, |
|||
LookDev.currentContext.GetViewContent(ViewIndex.Second).camera, |
|||
this, |
|||
index => |
|||
{ |
|||
LookDev.currentContext.SetFocusedCamera(index); |
|||
var environment = LookDev.currentContext.GetViewContent(index).environment; |
|||
if (sidePanel == SidePanel.Environment && environment != null) |
|||
m_EnvironmentList.selectedIndex = LookDev.currentContext.environmentLibrary.IndexOf(environment); |
|||
}); |
|||
var secondManipulator = new CameraController( |
|||
LookDev.currentContext.GetViewContent(ViewIndex.Second).camera, |
|||
this, |
|||
() => |
|||
{ |
|||
LookDev.currentContext.SetFocusedCamera(ViewIndex.Second); |
|||
var environment = LookDev.currentContext.GetViewContent(ViewIndex.Second).environment; |
|||
if (sidePanel == SidePanel.Environment && environment != null) |
|||
m_EnvironmentList.selectedIndex = LookDev.currentContext.environmentLibrary.IndexOf(environment); |
|||
}); |
|||
var gizmoManipulator = new ComparisonGizmoController(LookDev.currentContext.layout.gizmoState, firstOrCompositeManipulator); |
|||
m_Views[(int)ViewIndex.First].AddManipulator(gizmoManipulator); //must take event first to switch the firstOrCompositeManipulator
|
|||
m_Views[(int)ViewIndex.First].AddManipulator(firstOrCompositeManipulator); |
|||
m_Views[(int)ViewIndex.Second].AddManipulator(secondManipulator); |
|||
|
|||
m_NoObject1 = new Label("Drag'n'drop object here"); |
|||
m_NoObject1.style.flexGrow = 1; |
|||
m_NoObject1.style.unityTextAlign = TextAnchor.MiddleCenter; |
|||
m_NoObject2 = new Label("Drag'n'drop object here"); |
|||
m_NoObject2.style.flexGrow = 1; |
|||
m_NoObject2.style.unityTextAlign = TextAnchor.MiddleCenter; |
|||
m_NoEnvironment1 = new Label("Drag'n'drop environment from side panel here"); |
|||
m_NoEnvironment1.style.flexGrow = 1; |
|||
m_NoEnvironment1.style.unityTextAlign = TextAnchor.MiddleCenter; |
|||
m_NoEnvironment2 = new Label("Drag'n'drop environment from side panel here"); |
|||
m_NoEnvironment2.style.flexGrow = 1; |
|||
m_NoEnvironment2.style.unityTextAlign = TextAnchor.MiddleCenter; |
|||
m_Views[(int)ViewIndex.First].Add(m_NoObject1); |
|||
m_Views[(int)ViewIndex.First].Add(m_NoEnvironment1); |
|||
m_Views[(int)ViewIndex.Second].Add(m_NoObject2); |
|||
m_Views[(int)ViewIndex.Second].Add(m_NoEnvironment2); |
|||
} |
|||
|
|||
void CreateDropAreas() |
|||
{ |
|||
// GameObject or Prefab in view
|
|||
new DropArea(new[] { typeof(GameObject) }, m_Views[(int)ViewIndex.First], (obj, localPos) => |
|||
{ |
|||
if (layout == Layout.CustomSplit) |
|||
OnChangingObjectInViewInternal?.Invoke(obj as GameObject, ViewCompositionIndex.Composite, localPos); |
|||
else |
|||
OnChangingObjectInViewInternal?.Invoke(obj as GameObject, ViewCompositionIndex.First, localPos); |
|||
m_NoObject1.style.visibility = obj == null || obj.Equals(null) ? Visibility.Visible : Visibility.Hidden; |
|||
}); |
|||
new DropArea(new[] { typeof(GameObject) }, m_Views[(int)ViewIndex.Second], (obj, localPos) => |
|||
{ |
|||
OnChangingObjectInViewInternal?.Invoke(obj as GameObject, ViewCompositionIndex.Second, localPos); |
|||
m_NoObject2.style.visibility = obj == null || obj.Equals(null) ? Visibility.Visible : Visibility.Hidden; |
|||
}); |
|||
|
|||
// Material in view
|
|||
//new DropArea(new[] { typeof(GameObject) }, m_Views[(int)ViewIndex.First], (obj, localPos) =>
|
|||
//{
|
|||
// if (layout == Layout.CustomSplit || layout == Layout.CustomCircular)
|
|||
// OnChangingMaterialInViewInternal?.Invoke(obj as Material, ViewCompositionIndex.Composite, localPos);
|
|||
// else
|
|||
// OnChangingMaterialInViewInternal?.Invoke(obj as Material, ViewCompositionIndex.First, localPos);
|
|||
//});
|
|||
//new DropArea(new[] { typeof(Material) }, m_Views[(int)ViewIndex.Second], (obj, localPos)
|
|||
// => OnChangingMaterialInViewInternal?.Invoke(obj as Material, ViewCompositionIndex.Second, localPos));
|
|||
|
|||
// Environment in view
|
|||
new DropArea(new[] { typeof(Environment), typeof(Cubemap) }, m_Views[(int)ViewIndex.First], (obj, localPos) => |
|||
{ |
|||
if (layout == Layout.CustomSplit) |
|||
OnChangingEnvironmentInViewInternal?.Invoke(obj, ViewCompositionIndex.Composite, localPos); |
|||
else |
|||
OnChangingEnvironmentInViewInternal?.Invoke(obj, ViewCompositionIndex.First, localPos); |
|||
m_NoEnvironment1.style.visibility = obj == null || obj.Equals(null) ? Visibility.Visible : Visibility.Hidden; |
|||
}); |
|||
new DropArea(new[] { typeof(Environment), typeof(Cubemap) }, m_Views[(int)ViewIndex.Second], (obj, localPos) => |
|||
{ |
|||
OnChangingEnvironmentInViewInternal?.Invoke(obj, ViewCompositionIndex.Second, localPos); |
|||
m_NoEnvironment2.style.visibility = obj == null || obj.Equals(null) ? Visibility.Visible : Visibility.Hidden; |
|||
}); |
|||
|
|||
// Environment in library
|
|||
//new DropArea(new[] { typeof(Environment), typeof(Cubemap) }, m_EnvironmentContainer, (obj, localPos) =>
|
|||
//{
|
|||
// //[TODO: check if this come from outside of library]
|
|||
// OnAddingEnvironmentInternal?.Invoke(obj);
|
|||
//});
|
|||
new DropArea(new[] { typeof(EnvironmentLibrary) }, m_EnvironmentContainer, (obj, localPos) => |
|||
{ |
|||
OnChangingEnvironmentLibraryInternal?.Invoke(obj as EnvironmentLibrary); |
|||
RefreshLibraryDisplay(); |
|||
}); |
|||
} |
|||
|
|||
void CreateDebug() |
|||
{ |
|||
if (m_MainContainer == null || m_MainContainer.Equals(null)) |
|||
throw new System.MemberAccessException("m_MainContainer should be assigned prior CreateEnvironment()"); |
|||
|
|||
m_DebugContainer = new VisualElement() { name = k_DebugContainerName }; |
|||
m_MainContainer.Add(m_DebugContainer); |
|||
if (sidePanel == SidePanel.Debug) |
|||
m_MainContainer.AddToClassList(k_ShowDebugPanelClass); |
|||
|
|||
|
|||
//[TODO: finish]
|
|||
//Toggle greyBalls = new Toggle("Grey balls");
|
|||
//greyBalls.SetValueWithoutNotify(LookDev.currentContext.GetViewContent(LookDev.currentContext.layout.lastFocusedView).debug.greyBalls);
|
|||
//greyBalls.RegisterValueChangedCallback(evt =>
|
|||
//{
|
|||
// LookDev.currentContext.GetViewContent(LookDev.currentContext.layout.lastFocusedView).debug.greyBalls = evt.newValue;
|
|||
//});
|
|||
//m_DebugContainer.Add(greyBalls);
|
|||
|
|||
//[TODO: debug why list sometimes empty on resource reloading]
|
|||
//[TODO: display only per view]
|
|||
|
|||
RefreshDebugViews(); |
|||
} |
|||
|
|||
void RefreshDebugViews() |
|||
{ |
|||
if (m_DebugView != null && m_DebugContainer.Contains(m_DebugView)) |
|||
m_DebugContainer.Remove(m_DebugView); |
|||
|
|||
List<string> list = new List<string>(LookDev.dataProvider?.supportedDebugModes ?? Enumerable.Empty<string>()); |
|||
list.Insert(0, "None"); |
|||
m_DebugView = new PopupField<string>("Debug view mode", list, 0); |
|||
m_DebugView.RegisterValueChangedCallback(evt |
|||
=> LookDev.dataProvider.UpdateDebugMode(list.IndexOf(evt.newValue) - 1)); |
|||
m_DebugContainer.Add(m_DebugView); |
|||
} |
|||
|
|||
static int FirstVisibleIndex(ListView listView) |
|||
=> (int)(listView.Q<ScrollView>().scrollOffset.y / listView.itemHeight); |
|||
|
|||
void CreateEnvironment() |
|||
{ |
|||
if (m_MainContainer == null || m_MainContainer.Equals(null)) |
|||
throw new System.MemberAccessException("m_MainContainer should be assigned prior CreateEnvironment()"); |
|||
|
|||
m_EnvironmentContainer = new VisualElement() { name = k_EnvironmentContainerName }; |
|||
m_MainContainer.Add(m_EnvironmentContainer); |
|||
if (sidePanel == SidePanel.Environment) |
|||
m_MainContainer.AddToClassList(k_ShowEnvironmentPanelClass); |
|||
|
|||
m_EnvironmentInspector = new EnvironmentElement(withPreview: false, () => |
|||
{ |
|||
LookDev.SaveContextChangeAndApply(ViewIndex.First); |
|||
LookDev.SaveContextChangeAndApply(ViewIndex.Second); |
|||
}); |
|||
m_EnvironmentList = new ListView(); |
|||
m_EnvironmentList.AddToClassList("list-environment"); |
|||
m_EnvironmentList.selectionType = SelectionType.Single; |
|||
m_EnvironmentList.itemHeight = EnvironmentElement.k_SkyThumbnailHeight; |
|||
m_EnvironmentList.makeItem = () => |
|||
{ |
|||
var preview = new Image(); |
|||
preview.AddManipulator(new EnvironmentPreviewDragger(this, m_ViewContainer)); |
|||
return preview; |
|||
}; |
|||
m_EnvironmentList.bindItem = (e, i) => |
|||
{ |
|||
(e as Image).image = EnvironmentElement.GetLatLongThumbnailTexture( |
|||
LookDev.currentContext.environmentLibrary[i], |
|||
EnvironmentElement.k_SkyThumbnailWidth); |
|||
}; |
|||
m_EnvironmentList.onSelectionChanged += objects => |
|||
{ |
|||
if (objects.Count == 0 || (LookDev.currentContext.environmentLibrary?.Count ?? 0) == 0) |
|||
{ |
|||
m_EnvironmentInspector.style.visibility = Visibility.Hidden; |
|||
m_EnvironmentInspector.style.height = 0; |
|||
} |
|||
else |
|||
{ |
|||
m_EnvironmentInspector.style.visibility = Visibility.Visible; |
|||
m_EnvironmentInspector.style.height = new StyleLength(StyleKeyword.Auto); |
|||
int firstVisibleIndex = FirstVisibleIndex(m_EnvironmentList); |
|||
Environment environment = LookDev.currentContext.environmentLibrary[m_EnvironmentList.selectedIndex]; |
|||
var container = m_EnvironmentList.Q("unity-content-container"); |
|||
if (m_EnvironmentList.selectedIndex - firstVisibleIndex >= container.childCount || m_EnvironmentList.selectedIndex < firstVisibleIndex) |
|||
{ |
|||
m_EnvironmentList.ScrollToItem(m_EnvironmentList.selectedIndex); |
|||
firstVisibleIndex = FirstVisibleIndex(m_EnvironmentList); |
|||
} |
|||
Image deportedLatLong = container[m_EnvironmentList.selectedIndex - firstVisibleIndex] as Image; |
|||
m_EnvironmentInspector.Bind(environment, deportedLatLong); |
|||
} |
|||
}; |
|||
m_EnvironmentList.onItemChosen += obj => |
|||
EditorGUIUtility.PingObject(LookDev.currentContext.environmentLibrary[(int)obj]); |
|||
m_NoEnvironmentList = new Label("Drag'n'drop EnvironmentLibrary here"); |
|||
m_NoEnvironmentList.style.flexGrow = 1; |
|||
m_NoEnvironmentList.style.unityTextAlign = TextAnchor.MiddleCenter; |
|||
m_EnvironmentContainer.Add(m_EnvironmentInspector); |
|||
|
|||
m_EnvironmentListToolbar = new Toolbar(); |
|||
ToolbarButton addEnvironment = new ToolbarButton(() => |
|||
{ |
|||
LookDev.currentContext.environmentLibrary.Add(); |
|||
RefreshLibraryDisplay(); |
|||
m_EnvironmentList.ScrollToItem(-1); //-1: scroll to end
|
|||
m_EnvironmentList.selectedIndex = LookDev.currentContext.environmentLibrary.Count - 1; |
|||
ScrollToEnd(); |
|||
}) |
|||
{ |
|||
name = "add", |
|||
tooltip = "Add new empty environment" |
|||
}; |
|||
addEnvironment.Add(new Image() { image = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_EnvironmentAdd", forceLowRes: true) }); |
|||
ToolbarButton removeEnvironment = new ToolbarButton(() => |
|||
{ |
|||
if (m_EnvironmentList.selectedIndex == -1) |
|||
return; |
|||
LookDev.currentContext.environmentLibrary.Remove(m_EnvironmentList.selectedIndex); |
|||
RefreshLibraryDisplay(); |
|||
m_EnvironmentList.selectedIndex = -1; |
|||
}) |
|||
{ |
|||
name = "remove", |
|||
tooltip = "Remove environment currently selected" |
|||
}; |
|||
removeEnvironment.Add(new Image() { image = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_EnvironmentDelete", forceLowRes: true) }); |
|||
ToolbarButton duplicateEnvironment = new ToolbarButton(() => |
|||
{ |
|||
if (m_EnvironmentList.selectedIndex == -1) |
|||
return; |
|||
LookDev.currentContext.environmentLibrary.Duplicate(m_EnvironmentList.selectedIndex); |
|||
RefreshLibraryDisplay(); |
|||
m_EnvironmentList.ScrollToItem(-1); //-1: scroll to end
|
|||
m_EnvironmentList.selectedIndex = LookDev.currentContext.environmentLibrary.Count - 1; |
|||
ScrollToEnd(); |
|||
}) |
|||
{ |
|||
name = "duplicate", |
|||
tooltip = "Duplicate environment currently selected" |
|||
}; |
|||
duplicateEnvironment.Add(new Image() { image = CoreEditorUtils.LoadIcon(Style.k_IconFolder, "LookDev_EnvironmentDuplicate", forceLowRes: true) }); |
|||
m_EnvironmentListToolbar.Add(addEnvironment); |
|||
m_EnvironmentListToolbar.Add(removeEnvironment); |
|||
m_EnvironmentListToolbar.Add(duplicateEnvironment); |
|||
m_EnvironmentListToolbar.AddToClassList("list-environment-overlay"); |
|||
|
|||
var m_EnvironmentInspectorSeparator = new VisualElement() { name = "separator-line" }; |
|||
m_EnvironmentInspectorSeparator.Add(new VisualElement() { name = "separator" }); |
|||
m_EnvironmentContainer.Add(m_EnvironmentInspectorSeparator); |
|||
|
|||
VisualElement listContainer = new VisualElement(); |
|||
listContainer.AddToClassList("list-environment"); |
|||
listContainer.Add(m_EnvironmentList); |
|||
listContainer.Add(m_EnvironmentListToolbar); |
|||
|
|||
var environmentListCreationToolbar = new Toolbar() |
|||
{ |
|||
name = "environmentListCreationToolbar" |
|||
}; |
|||
environmentListCreationToolbar.Add(new ToolbarButton(() |
|||
=> EnvironmentLibraryCreator.Create()) |
|||
{ |
|||
text = "New Library", |
|||
tooltip = "Create a new EnvironmentLibrary" |
|||
}); |
|||
environmentListCreationToolbar.Add(new ToolbarButton(() |
|||
=> EnvironmentLibraryLoader.Load(RefreshLibraryDisplay)) |
|||
{ |
|||
text = "Load Library", |
|||
tooltip = "Load an existing EnvironmentLibrary" |
|||
}); |
|||
|
|||
m_EnvironmentContainer.Add(listContainer); |
|||
m_EnvironmentContainer.Add(m_NoEnvironmentList); |
|||
m_EnvironmentContainer.Add(environmentListCreationToolbar); |
|||
|
|||
//add ability to unselect
|
|||
m_EnvironmentList.RegisterCallback<MouseDownEvent>(evt => |
|||
{ |
|||
var clickedIndex = (int)(evt.localMousePosition.y / m_EnvironmentList.itemHeight); |
|||
if (clickedIndex >= m_EnvironmentList.itemsSource.Count) |
|||
{ |
|||
m_EnvironmentList.selectedIndex = -1; |
|||
evt.StopPropagation(); |
|||
} |
|||
}); |
|||
|
|||
RefreshLibraryDisplay(); |
|||
} |
|||
|
|||
//necessary as the scrollview need to be updated which take some editor frames.
|
|||
void ScrollToEnd(int attemptRemaining = 5) |
|||
{ |
|||
m_EnvironmentList.ScrollToItem(-1); //-1: scroll to end
|
|||
if (attemptRemaining > 0) |
|||
EditorApplication.delayCall += () => ScrollToEnd(--attemptRemaining); |
|||
} |
|||
|
|||
void RefreshLibraryDisplay() |
|||
{ |
|||
int itemMax = LookDev.currentContext.environmentLibrary?.Count ?? 0; |
|||
if (itemMax == 0 || m_EnvironmentList.selectedIndex == -1) |
|||
{ |
|||
m_EnvironmentInspector.style.visibility = Visibility.Hidden; |
|||
m_EnvironmentInspector.style.height = 0; |
|||
} |
|||
else |
|||
{ |
|||
m_EnvironmentInspector.style.visibility = Visibility.Visible; |
|||
m_EnvironmentInspector.style.height = new StyleLength(StyleKeyword.Auto); |
|||
} |
|||
var items = new List<int>(itemMax); |
|||
for (int i = 0; i < itemMax; i++) |
|||
items.Add(i); |
|||
m_EnvironmentList.itemsSource = items; |
|||
if (LookDev.currentContext.environmentLibrary == null) |
|||
{ |
|||
m_EnvironmentList |
|||
.Q(className: "unity-scroll-view__vertical-scroller") |
|||
.Q("unity-dragger") |
|||
.style.visibility = Visibility.Hidden; |
|||
m_EnvironmentListToolbar.style.visibility = Visibility.Hidden; |
|||
m_NoEnvironmentList.style.display = DisplayStyle.Flex; |
|||
} |
|||
else |
|||
{ |
|||
m_EnvironmentList |
|||
.Q(className: "unity-scroll-view__vertical-scroller") |
|||
.Q("unity-dragger") |
|||
.style.visibility = itemMax == 0 |
|||
? Visibility.Hidden |
|||
: Visibility.Visible; |
|||
m_EnvironmentListToolbar.style.visibility = Visibility.Visible; |
|||
m_NoEnvironmentList.style.display = DisplayStyle.None; |
|||
} |
|||
} |
|||
|
|||
DraggingContext StartDragging(VisualElement item, Vector2 worldPosition) |
|||
=> new DraggingContext( |
|||
rootVisualElement, |
|||
item as Image, |
|||
//note: this even can come before the selection event of the
|
|||
//ListView. Reconstruct index by looking at target of the event.
|
|||
(int)item.layout.y / m_EnvironmentList.itemHeight, |
|||
worldPosition); |
|||
|
|||
void EndDragging(DraggingContext context, Vector2 mouseWorldPosition) |
|||
{ |
|||
Environment environment = LookDev.currentContext.environmentLibrary[context.draggedIndex]; |
|||
if (m_Views[(int)ViewIndex.First].ContainsPoint(mouseWorldPosition)) |
|||
{ |
|||
if (layout == Layout.CustomSplit) |
|||
OnChangingEnvironmentInViewInternal?.Invoke(environment, ViewCompositionIndex.Composite, mouseWorldPosition); |
|||
else |
|||
OnChangingEnvironmentInViewInternal?.Invoke(environment, ViewCompositionIndex.First, mouseWorldPosition); |
|||
} |
|||
else |
|||
OnChangingEnvironmentInViewInternal?.Invoke(environment, ViewCompositionIndex.Second, mouseWorldPosition); |
|||
} |
|||
|
|||
class DraggingContext : IDisposable |
|||
{ |
|||
const string k_CursorFollowerName = "cursorFollower"; |
|||
public readonly int draggedIndex; |
|||
readonly Image cursorFollower; |
|||
readonly Vector2 cursorOffset; |
|||
readonly VisualElement windowContent; |
|||
|
|||
public DraggingContext(VisualElement windowContent, Image draggedElement, int draggedIndex, Vector2 worldPosition) |
|||
{ |
|||
this.windowContent = windowContent; |
|||
this.draggedIndex = draggedIndex; |
|||
cursorFollower = new Image() |
|||
{ |
|||
name = k_CursorFollowerName, |
|||
image = draggedElement.image |
|||
}; |
|||
cursorFollower.tintColor = new Color(1f, 1f, 1f, .5f); |
|||
windowContent.Add(cursorFollower); |
|||
cursorOffset = draggedElement.WorldToLocal(worldPosition); |
|||
|
|||
cursorFollower.style.position = Position.Absolute; |
|||
UpdateCursorFollower(worldPosition); |
|||
} |
|||
|
|||
public void UpdateCursorFollower(Vector2 mouseWorldPosition) |
|||
{ |
|||
Vector2 windowLocalPosition = windowContent.WorldToLocal(mouseWorldPosition); |
|||
cursorFollower.style.left = windowLocalPosition.x - cursorOffset.x; |
|||
cursorFollower.style.top = windowLocalPosition.y - cursorOffset.y; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (windowContent.Contains(cursorFollower)) |
|||
windowContent.Remove(cursorFollower); |
|||
} |
|||
} |
|||
|
|||
class EnvironmentPreviewDragger : Manipulator |
|||
{ |
|||
VisualElement m_DropArea; |
|||
DisplayWindow m_Window; |
|||
|
|||
//Note: static as only one drag'n'drop at a time
|
|||
static DraggingContext s_Context; |
|||
|
|||
public EnvironmentPreviewDragger(DisplayWindow window, VisualElement dropArea) |
|||
{ |
|||
m_Window = window; |
|||
m_DropArea = dropArea; |
|||
} |
|||
|
|||
protected override void RegisterCallbacksOnTarget() |
|||
{ |
|||
target.RegisterCallback<MouseDownEvent>(OnMouseDown); |
|||
target.RegisterCallback<MouseUpEvent>(OnMouseUp); |
|||
} |
|||
|
|||
protected override void UnregisterCallbacksFromTarget() |
|||
{ |
|||
target.UnregisterCallback<MouseDownEvent>(OnMouseDown); |
|||
target.UnregisterCallback<MouseUpEvent>(OnMouseUp); |
|||
} |
|||
|
|||
void Release() |
|||
{ |
|||
target.UnregisterCallback<MouseMoveEvent>(OnMouseMove); |
|||
s_Context.Dispose(); |
|||
target.ReleaseMouse(); |
|||
s_Context = null; |
|||
} |
|||
|
|||
void OnMouseDown(MouseDownEvent evt) |
|||
{ |
|||
if (evt.button == 0) |
|||
{ |
|||
target.CaptureMouse(); |
|||
target.RegisterCallback<MouseMoveEvent>(OnMouseMove); |
|||
s_Context = m_Window.StartDragging(target, evt.mousePosition); |
|||
//do not stop event as we still need to propagate it to the ListView for selection
|
|||
} |
|||
} |
|||
|
|||
void OnMouseUp(MouseUpEvent evt) |
|||
{ |
|||
if (evt.button != 0) |
|||
return; |
|||
if (m_DropArea.ContainsPoint(m_DropArea.WorldToLocal(Event.current.mousePosition))) |
|||
{ |
|||
m_Window.EndDragging(s_Context, evt.mousePosition); |
|||
evt.StopPropagation(); |
|||
} |
|||
Release(); |
|||
} |
|||
|
|||
void OnMouseMove(MouseMoveEvent evt) |
|||
{ |
|||
evt.StopPropagation(); |
|||
s_Context.UpdateCursorFollower(evt.mousePosition); |
|||
} |
|||
} |
|||
|
|||
Rect IViewDisplayer.GetRect(ViewCompositionIndex index) |
|||
{ |
|||
switch (index) |
|||
{ |
|||
case ViewCompositionIndex.First: |
|||
case ViewCompositionIndex.Composite: //display composition on first rect
|
|||
return m_Views[(int)ViewIndex.First].contentRect; |
|||
case ViewCompositionIndex.Second: |
|||
return m_Views[(int)ViewIndex.Second].contentRect; |
|||
default: |
|||
throw new ArgumentException("Unknown ViewCompositionIndex: " + index); |
|||
} |
|||
} |
|||
|
|||
Vector2 m_LastFirstViewSize = new Vector2(); |
|||
Vector2 m_LastSecondViewSize = new Vector2(); |
|||
void IViewDisplayer.SetTexture(ViewCompositionIndex index, Texture texture) |
|||
{ |
|||
bool updated = false; |
|||
switch (index) |
|||
{ |
|||
case ViewCompositionIndex.First: |
|||
case ViewCompositionIndex.Composite: //display composition on first rect
|
|||
if (updated |= m_Views[(int)ViewIndex.First].image != texture) |
|||
m_Views[(int)ViewIndex.First].image = texture; |
|||
else if (updated |= (m_LastFirstViewSize.x != texture.width |
|||
|| m_LastFirstViewSize.y != texture.height)) |
|||
{ |
|||
m_Views[(int)ViewIndex.First].image = null; //force refresh else it will appear zoomed
|
|||
m_Views[(int)ViewIndex.First].image = texture; |
|||
} |
|||
if (updated) |
|||
{ |
|||
m_LastFirstViewSize.x = texture?.width ?? 0; |
|||
m_LastFirstViewSize.y = texture?.height ?? 0; |
|||
} |
|||
break; |
|||
case ViewCompositionIndex.Second: |
|||
if (m_Views[(int)ViewIndex.Second].image != texture) |
|||
m_Views[(int)ViewIndex.Second].image = texture; |
|||
else if (updated |= (m_LastSecondViewSize.x != texture.width |
|||
|| m_LastSecondViewSize.y != texture.height)) |
|||
{ |
|||
m_Views[(int)ViewIndex.Second].image = null; //force refresh else it will appear zoomed
|
|||
m_Views[(int)ViewIndex.Second].image = texture; |
|||
} |
|||
if (updated) |
|||
{ |
|||
m_LastSecondViewSize.x = texture?.width ?? 0; |
|||
m_LastSecondViewSize.y = texture?.height ?? 0; |
|||
} |
|||
break; |
|||
default: |
|||
throw new ArgumentException("Unknown ViewCompositionIndex: " + index); |
|||
} |
|||
} |
|||
|
|||
void IViewDisplayer.Repaint() => Repaint(); |
|||
|
|||
void IEnvironmentDisplayer.Repaint() |
|||
=> RefreshLibraryDisplay(); |
|||
|
|||
void ApplyLayout(Layout value) |
|||
{ |
|||
m_NoObject1.style.visibility = LookDev.currentContext.GetViewContent(ViewIndex.First).hasViewedObject ? Visibility.Hidden : Visibility.Visible; |
|||
m_NoObject2.style.visibility = LookDev.currentContext.GetViewContent(ViewIndex.Second).hasViewedObject ? Visibility.Hidden : Visibility.Visible; |
|||
m_NoEnvironment1.style.visibility = LookDev.currentContext.GetViewContent(ViewIndex.First).hasEnvironment ? Visibility.Hidden : Visibility.Visible; |
|||
m_NoEnvironment2.style.visibility = LookDev.currentContext.GetViewContent(ViewIndex.Second).hasEnvironment ? Visibility.Hidden : Visibility.Visible; |
|||
|
|||
switch (value) |
|||
{ |
|||
case Layout.HorizontalSplit: |
|||
case Layout.VerticalSplit: |
|||
if (!m_ViewContainer.ClassListContains(k_FirstViewClass)) |
|||
m_ViewContainer.AddToClassList(k_FirstViewClass); |
|||
if (!m_ViewContainer.ClassListContains(k_SecondViewsClass)) |
|||
m_ViewContainer.AddToClassList(k_SecondViewsClass); |
|||
if (value == Layout.VerticalSplit) |
|||
{ |
|||
m_ViewContainer.AddToClassList(k_VerticalViewsClass); |
|||
if (!m_ViewContainer.ClassListContains(k_VerticalViewsClass)) |
|||
m_ViewContainer.AddToClassList(k_FirstViewClass); |
|||
} |
|||
for (int i = 0; i < 2; ++i) |
|||
m_Views[i].style.display = DisplayStyle.Flex; |
|||
break; |
|||
case Layout.FullFirstView: |
|||
case Layout.CustomSplit: //display composition on first rect
|
|||
if (!m_ViewContainer.ClassListContains(k_FirstViewClass)) |
|||
m_ViewContainer.AddToClassList(k_FirstViewClass); |
|||
if (m_ViewContainer.ClassListContains(k_SecondViewsClass)) |
|||
m_ViewContainer.RemoveFromClassList(k_SecondViewsClass); |
|||
m_Views[0].style.display = DisplayStyle.Flex; |
|||
m_Views[1].style.display = DisplayStyle.None; |
|||
break; |
|||
case Layout.FullSecondView: |
|||
if (m_ViewContainer.ClassListContains(k_FirstViewClass)) |
|||
m_ViewContainer.RemoveFromClassList(k_FirstViewClass); |
|||
if (!m_ViewContainer.ClassListContains(k_SecondViewsClass)) |
|||
m_ViewContainer.AddToClassList(k_SecondViewsClass); |
|||
m_Views[0].style.display = DisplayStyle.None; |
|||
m_Views[1].style.display = DisplayStyle.Flex; |
|||
break; |
|||
default: |
|||
throw new ArgumentException("Unknown Layout"); |
|||
} |
|||
|
|||
//Add flex direction here
|
|||
if (value == Layout.VerticalSplit) |
|||
m_ViewContainer.AddToClassList(k_VerticalViewsClass); |
|||
else if (m_ViewContainer.ClassListContains(k_VerticalViewsClass)) |
|||
m_ViewContainer.RemoveFromClassList(k_VerticalViewsClass); |
|||
} |
|||
|
|||
void ApplySidePanelChange(SidePanel sidePanel) |
|||
{ |
|||
switch (sidePanel) |
|||
{ |
|||
case SidePanel.None: |
|||
if (m_MainContainer.ClassListContains(k_ShowEnvironmentPanelClass)) |
|||
m_MainContainer.RemoveFromClassList(k_ShowEnvironmentPanelClass); |
|||
if (m_MainContainer.ClassListContains(k_ShowDebugPanelClass)) |
|||
m_MainContainer.RemoveFromClassList(k_ShowDebugPanelClass); |
|||
m_EnvironmentContainer.Q<EnvironmentElement>().style.visibility = Visibility.Hidden; |
|||
m_EnvironmentContainer.Q(className: "unity-base-slider--vertical").Q("unity-dragger").style.display = DisplayStyle.None; |
|||
m_EnvironmentContainer.style.display = DisplayStyle.None; |
|||
break; |
|||
case SidePanel.Environment: |
|||
if (!m_MainContainer.ClassListContains(k_ShowEnvironmentPanelClass)) |
|||
m_MainContainer.AddToClassList(k_ShowEnvironmentPanelClass); |
|||
if (m_MainContainer.ClassListContains(k_ShowDebugPanelClass)) |
|||
m_MainContainer.RemoveFromClassList(k_ShowDebugPanelClass); |
|||
if (m_EnvironmentList.selectedIndex != -1) |
|||
m_EnvironmentContainer.Q<EnvironmentElement>().style.visibility = Visibility.Visible; |
|||
m_EnvironmentContainer.Q(className: "unity-base-slider--vertical").Q("unity-dragger").style.display = DisplayStyle.Flex; |
|||
m_EnvironmentContainer.style.display = DisplayStyle.Flex; |
|||
break; |
|||
case SidePanel.Debug: |
|||
if (m_MainContainer.ClassListContains(k_ShowEnvironmentPanelClass)) |
|||
m_MainContainer.RemoveFromClassList(k_ShowEnvironmentPanelClass); |
|||
if (!m_MainContainer.ClassListContains(k_ShowDebugPanelClass)) |
|||
m_MainContainer.AddToClassList(k_ShowDebugPanelClass); |
|||
m_EnvironmentContainer.Q<EnvironmentElement>().style.visibility = Visibility.Hidden; |
|||
m_EnvironmentContainer.Q(className: "unity-base-slider--vertical").Q("unity-dragger").style.display = DisplayStyle.None; |
|||
m_EnvironmentContainer.style.display = DisplayStyle.None; |
|||
break; |
|||
default: |
|||
throw new ArgumentException("Unknown SidePanel"); |
|||
} |
|||
} |
|||
|
|||
void OnGUI() => OnUpdateRequestedInternal?.Invoke(); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 3c06d176f822d384683da5237d832c67 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
.container |
|||
{ |
|||
margin: 0px; |
|||
flex: 1; |
|||
flex-direction: row; |
|||
} |
|||
|
|||
/* ///// */ |
|||
/* VIEWS */ |
|||
/* ///// */ |
|||
|
|||
/* override as later in document */ |
|||
.verticalSplit |
|||
{ |
|||
flex-direction: column; |
|||
} |
|||
|
|||
#viewContainer |
|||
{ |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
#firstView, |
|||
#secondView |
|||
{ |
|||
flex: 0; |
|||
} |
|||
|
|||
/* override as later in document */ |
|||
.firstView > #firstView, |
|||
.secondView > #secondView |
|||
{ |
|||
flex: 1; |
|||
} |
|||
|
|||
|
|||
/* /////////// */ |
|||
/* ENVIRONMENT */ |
|||
/* /////////// */ |
|||
|
|||
#environmentContainer |
|||
{ |
|||
width: 0px; |
|||
visibility: hidden; |
|||
flex-direction: column-reverse; |
|||
} |
|||
|
|||
#debugContainer |
|||
{ |
|||
width: 0px; |
|||
visibility: hidden; |
|||
} |
|||
|
|||
#environmentContainer > EnvironmentElement |
|||
{ |
|||
border-color: #232323; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.showEnvironmentPanel > #environmentContainer |
|||
{ |
|||
width: 250px; /*219px;*/ |
|||
visibility: visible; |
|||
} |
|||
|
|||
.showDebugPanel > #debugContainer |
|||
{ |
|||
width: 250px; /*219px;*/ |
|||
visibility: visible; |
|||
} |
|||
|
|||
.unity-label |
|||
{ |
|||
min-width: 100px; |
|||
} |
|||
|
|||
.unity-composite-field__field > .unity-base-field__label |
|||
{ |
|||
min-width: 10px; |
|||
} |
|||
|
|||
#unity-text-input |
|||
{ |
|||
min-width: 40px; |
|||
} |
|||
|
|||
.list-environment |
|||
{ |
|||
flex: 1; |
|||
} |
|||
|
|||
.list-environment-overlay |
|||
{ |
|||
position: absolute; |
|||
bottom: 0px; |
|||
padding-left: 1px; |
|||
border-bottom-width: 0px; |
|||
background-color: rgba(0,0,0,0); |
|||
} |
|||
|
|||
#environmentListCreationToolbar |
|||
{ |
|||
padding-left: 1px; |
|||
} |
|||
|
|||
#environmentListCreationToolbar > * |
|||
{ |
|||
flex: 1; |
|||
-unity-text-align: middle-center; |
|||
} |
|||
|
|||
ToolbarButton |
|||
{ |
|||
border-left-width: 0px; |
|||
} |
|||
|
|||
.list-environment-overlay > ToolbarButton |
|||
{ |
|||
border-width: 0px; |
|||
border-right-width: 1px; |
|||
width: 20px; |
|||
min-width: 20px; |
|||
-unity-text-align: middle-center; |
|||
background-color: #3c3c3c; |
|||
} |
|||
|
|||
.list-environment-overlay > #duplicate |
|||
{ |
|||
border-right-width: 0px; |
|||
} |
|||
|
|||
|
|||
|
|||
Image.unity-list-view__item |
|||
{ |
|||
width: 210px; |
|||
margin: 15px; |
|||
padding: 5px; |
|||
} |
|||
|
|||
Image.unity-list-view__item:selected |
|||
{ |
|||
border-width: 2px; |
|||
padding: 3px; |
|||
border-color: #3d6091; |
|||
background-color: rgba(0,0,0,0); |
|||
} |
|||
|
|||
.sun-to-brightest-button |
|||
{ |
|||
padding-left: 4px; |
|||
} |
|||
|
|||
#inspector-header |
|||
{ |
|||
flex-direction: row; |
|||
border-bottom-width: 1px; |
|||
border-color: #232323; |
|||
padding-top: 5px; |
|||
padding-bottom: 5px; |
|||
padding-left: 3px; |
|||
background-color: #3C3C3C; |
|||
} |
|||
|
|||
#inspector-header > Image |
|||
{ |
|||
margin-top: 2px; |
|||
margin-bottom: 2px; |
|||
} |
|||
|
|||
#inspector-header > TextField |
|||
{ |
|||
flex: 1; |
|||
} |
|||
|
|||
#inspector |
|||
{ |
|||
padding-bottom: 5px; |
|||
} |
|||
|
|||
#separator-line |
|||
{ |
|||
background-color: #3C3C3C; |
|||
} |
|||
|
|||
#separator |
|||
{ |
|||
flex: 1; |
|||
width: 190px; |
|||
border-bottom-width: 1px; |
|||
border-color: #232323; |
|||
height: 0; |
|||
align-self: flex-end; |
|||
} |
|||
|
|||
#sunToBrightestButton |
|||
{ |
|||
background-color: rgba(0,0,0,0); |
|||
border-radius: 0px; |
|||
border-width: 0px; |
|||
padding: 0px; |
|||
margin-right: 1px; |
|||
} |
|||
|
|||
/* /////// */ |
|||
/* TOOLBAR */ |
|||
/* /////// */ |
|||
|
|||
#toolbar |
|||
{ |
|||
flex-direction: row; |
|||
} |
|||
|
|||
#toolbar > #toolbarRadio |
|||
{ |
|||
flex-direction: row; |
|||
} |
|||
|
|||
.unity-toggle__input > .unity-image |
|||
{ |
|||
padding: 2px; |
|||
} |
|||
|
|||
#toolbar > * > .unity-label |
|||
{ |
|||
padding-top: 3px; |
|||
padding-bottom: 0px; |
|||
min-width: 30px; |
|||
} |
|||
|
|||
#sideToolbar |
|||
{ |
|||
width: 249px; |
|||
} |
|||
|
|||
#sideToolbar > #tabsRadio |
|||
{ |
|||
min-width: auto; |
|||
flex: 1; |
|||
flex-direction: row; |
|||
-unity-text-align: middle-center; |
|||
} |
|||
|
|||
.unity-toolbar-toggle |
|||
{ |
|||
padding-top: 0px; |
|||
padding-right: 0px; |
|||
padding-bottom: 0px; |
|||
padding-left: 0px; |
|||
margin-left: 0px; |
|||
margin-right: 0px; |
|||
border-left-width: 0px; |
|||
} |
|||
|
|||
#renderdoc-content |
|||
{ |
|||
flex: 1; |
|||
flex-direction: row; |
|||
max-width: 80px; |
|||
flex-shrink: 0; |
|||
min-width: 24px; |
|||
} |
|||
|
|||
#renderdoc-content > Label |
|||
{ |
|||
-unity-text-align: middle-left; |
|||
min-width: 0px; |
|||
} |
|||
|
|||
#renderdoc-content > Image |
|||
{ |
|||
flex-shrink: 0; |
|||
min-width: 16px; |
|||
} |
|||
|
|||
#cameraMenu |
|||
{ |
|||
flex-direction: row; |
|||
padding: 0px; |
|||
padding-right: 16px; |
|||
} |
|||
|
|||
#cameraButton |
|||
{ |
|||
border-radius: 0px; |
|||
border-width: 0px; |
|||
border-left-width: 1px; |
|||
padding: 0px; |
|||
padding-right: 4px; |
|||
margin: 0px; |
|||
} |
|||
|
|||
#cameraSeparator |
|||
{ |
|||
margin-top: 4px; |
|||
margin-bottom: 4px; |
|||
border-right-width: 1px; |
|||
} |
|||
|
|||
#cameraButton > * |
|||
{ |
|||
margin: 2px; |
|||
} |
|||
|
|||
/* /////////// */ |
|||
/* DRAG'N'DROP */ |
|||
/* /////////// */ |
|||
|
|||
#cursorFollower |
|||
{ |
|||
position: absolute; |
|||
} |
|||
|
|||
|
|||
|
|||
/* TEST UNDER THIS LINE */ |
|||
#firstView |
|||
{ |
|||
background-color: rgb(255, 0, 0); |
|||
} |
|||
|
|||
#secondView |
|||
{ |
|||
background-color: rgb(0, 255, 0); |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: e78887bb3654fde48bc3bc31beb7c7e4 |
|||
ScriptedImporter: |
|||
internalIDToNameTable: [] |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|||
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} |
|||
disableValidation: 0 |
|
|||
using System.Collections; |
|||
using System; |
|||
using UnityEngine; |
|||
using UnityEngine.UIElements; |
|||
|
|||
namespace UnityEditor.Rendering.LookDev |
|||
{ |
|||
class DropArea |
|||
{ |
|||
readonly Type[] k_AcceptedTypes; |
|||
bool droppable; |
|||
|
|||
public DropArea(Type[] acceptedTypes, VisualElement area, Action<UnityEngine.Object, Vector2> OnDrop) |
|||
{ |
|||
k_AcceptedTypes = acceptedTypes; |
|||
area.RegisterCallback<DragPerformEvent>(evt => Drop(evt, OnDrop)); |
|||
area.RegisterCallback<DragEnterEvent>(evt => DragEnter(evt)); |
|||
area.RegisterCallback<DragLeaveEvent>(evt => DragLeave(evt)); |
|||
area.RegisterCallback<DragExitedEvent>(evt => DragExit(evt)); |
|||
area.RegisterCallback<DragUpdatedEvent>(evt => DragUpdate(evt)); |
|||
} |
|||
|
|||
void DragEnter(DragEnterEvent evt) |
|||
{ |
|||
droppable = false; |
|||
foreach (UnityEngine.Object obj in DragAndDrop.objectReferences) |
|||
{ |
|||
if (!IsInAcceptedTypes(obj.GetType())) |
|||
continue; |
|||
|
|||
droppable = true; |
|||
evt.StopPropagation(); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
void DragLeave(DragLeaveEvent evt) |
|||
{ |
|||
foreach (UnityEngine.Object obj in DragAndDrop.objectReferences) |
|||
{ |
|||
if (!IsInAcceptedTypes(obj.GetType())) |
|||
continue; |
|||
|
|||
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected; |
|||
evt.StopPropagation(); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
void DragExit(DragExitedEvent evt) |
|||
{ |
|||
foreach (UnityEngine.Object obj in DragAndDrop.objectReferences) |
|||
{ |
|||
if (!IsInAcceptedTypes(obj.GetType())) |
|||
continue; |
|||
|
|||
DragAndDrop.visualMode = DragAndDropVisualMode.Rejected; |
|||
evt.StopPropagation(); |
|||
return; |
|||
} |
|||
} |
|||
|
|||
void DragUpdate(DragUpdatedEvent evt) |
|||
{ |
|||
foreach (UnityEngine.Object obj in DragAndDrop.objectReferences) |
|||
{ |
|||
if (!IsInAcceptedTypes(obj.GetType())) |
|||
continue; |
|||
|
|||
DragAndDrop.visualMode = droppable ? DragAndDropVisualMode.Link : DragAndDropVisualMode.Rejected; |
|||
evt.StopPropagation(); |
|||
} |
|||
} |
|||
|
|||
void Drop(DragPerformEvent evt, Action<UnityEngine.Object, Vector2> OnDrop) |
|||
{ |
|||
bool atLeastOneAccepted = false; |
|||
foreach (UnityEngine.Object obj in DragAndDrop.objectReferences) |
|||
{ |
|||
if (!IsInAcceptedTypes(obj.GetType())) |
|||
continue; |
|||
|
|||
OnDrop.Invoke(obj, evt.localMousePosition); |
|||
atLeastOneAccepted = true; |
|||
} |
|||
if (atLeastOneAccepted) |
|||
{ |
|||
DragAndDrop.AcceptDrag(); |
|||
evt.StopPropagation(); |
|||
} |
|||
} |
|||
|
|||
bool IsInAcceptedTypes(Type testedType) |
|||
{ |
|||
foreach (Type type in k_AcceptedTypes) |
|||
{ |
|||
if (testedType.IsAssignableFrom(type)) |
|||
return true; |
|||
} |
|||
return false; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 775ab08d64fdb694d89087d33fce3216 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
using System; |
|||
using UnityEngine.UIElements; |
|||
using UnityEditor.UIElements; |
|||
using System.Linq; |
|||
|
|||
namespace UnityEditor.Rendering.LookDev |
|||
{ |
|||
/// <summary>
|
|||
/// Lighting environment used in LookDev
|
|||
/// </summary>
|
|||
public class Environment : ScriptableObject |
|||
{ |
|||
[Serializable] |
|||
public abstract class BaseEnvironmentCubemapHandler |
|||
{ |
|||
[SerializeField] |
|||
string m_CubemapGUID; |
|||
Cubemap m_Cubemap; |
|||
|
|||
/// <summary>
|
|||
/// The cubemap used for this part of the lighting environment
|
|||
/// </summary>
|
|||
public Cubemap cubemap |
|||
{ |
|||
get |
|||
{ |
|||
if (m_Cubemap == null || m_Cubemap.Equals(null)) |
|||
LoadCubemap(); |
|||
return m_Cubemap; |
|||
} |
|||
set |
|||
{ |
|||
m_Cubemap = value; |
|||
m_CubemapGUID = AssetDatabase.AssetPathToGUID(AssetDatabase.GetAssetPath(m_Cubemap)); |
|||
} |
|||
} |
|||
|
|||
void LoadCubemap() |
|||
{ |
|||
m_Cubemap = null; |
|||
|
|||
GUID storedGUID; |
|||
GUID.TryParse(m_CubemapGUID, out storedGUID); |
|||
if (!storedGUID.Empty()) |
|||
{ |
|||
string path = AssetDatabase.GUIDToAssetPath(m_CubemapGUID); |
|||
m_Cubemap = AssetDatabase.LoadAssetAtPath<Cubemap>(path); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Class containing editor data for shadow part of the lighting environment
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public class Shadow : BaseEnvironmentCubemapHandler |
|||
{ |
|||
// Setup default position to be on the sun in the default HDRI.
|
|||
// This is important as the defaultHDRI don't call the set brightest spot function on first call.
|
|||
[SerializeField] |
|||
float m_Latitude = 60.0f; // [-90..90]
|
|||
[SerializeField] |
|||
float m_Longitude = 299.0f; // [0..360[
|
|||
|
|||
/// <summary>
|
|||
/// The shading tint to used when computing shadow from sun
|
|||
/// </summary>
|
|||
public Color color = Color.white; |
|||
|
|||
/// <summary>
|
|||
/// The Latitude position of the sun casting shadows
|
|||
/// </summary>
|
|||
public float sunLatitude |
|||
{ |
|||
get => m_Latitude; |
|||
set => m_Latitude = ClampLatitude(value); |
|||
} |
|||
|
|||
internal static float ClampLatitude(float value) => Mathf.Clamp(value, -90, 90); |
|||
|
|||
/// <summary>
|
|||
/// The Longitude position of the sun casting shadows
|
|||
/// </summary>
|
|||
public float sunLongitude |
|||
{ |
|||
get => m_Longitude; |
|||
set => m_Longitude = ClampLongitude(value); |
|||
} |
|||
|
|||
internal static float ClampLongitude(float value) |
|||
{ |
|||
value = value % 360f; |
|||
if (value < 0.0) |
|||
value += 360f; |
|||
return value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Class containing editor data for sky part of the lighting environment
|
|||
/// </summary>
|
|||
[Serializable] |
|||
public class Sky : BaseEnvironmentCubemapHandler |
|||
{ |
|||
/// <summary>
|
|||
/// Offset on the longitude. Affect both sky and sun position in Shadow part
|
|||
/// </summary>
|
|||
public float rotation = 0.0f; |
|||
/// <summary>
|
|||
/// Exposure to use with this Sky
|
|||
/// </summary>
|
|||
public float exposure = 1f; |
|||
|
|||
/// <summary>
|
|||
/// Implicit conversion operator to runtime version of sky datas
|
|||
/// </summary>
|
|||
/// <param name="sky">Editor version of the datas</param>
|
|||
public static implicit operator UnityEngine.Rendering.LookDev.Sky(Sky sky) |
|||
=> sky == null |
|||
? default |
|||
: new UnityEngine.Rendering.LookDev.Sky() |
|||
{ |
|||
cubemap = sky.cubemap, |
|||
longitudeOffset = sky.rotation, |
|||
exposure = sky.exposure |
|||
}; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The sky part of the lighting environment
|
|||
/// </summary>
|
|||
public Sky sky = new Sky(); |
|||
|
|||
/// <summary>
|
|||
/// The shadow part of the lighting environment
|
|||
/// </summary>
|
|||
public Shadow shadow = new Shadow(); |
|||
|
|||
/// <summary>
|
|||
/// Compute the shadow runtime data with editor datas
|
|||
/// </summary>
|
|||
public UnityEngine.Rendering.LookDev.Sky shadowSky |
|||
=> new UnityEngine.Rendering.LookDev.Sky() |
|||
{ |
|||
cubemap = shadow.cubemap ?? sky.cubemap, |
|||
longitudeOffset = sky.rotation, |
|||
exposure = sky.exposure |
|||
}; |
|||
|
|||
internal float shadowIntensity |
|||
=> shadow.cubemap == null ? 0.3f : 1f; |
|||
|
|||
internal void UpdateSunPosition(Light sun) |
|||
=> sun.transform.rotation = Quaternion.Euler(shadow.sunLatitude, sky.rotation + shadow.sunLongitude, 0f); |
|||
|
|||
internal void CopyTo(Environment other) |
|||
{ |
|||
other.sky.cubemap = sky.cubemap; |
|||
other.sky.exposure = sky.exposure; |
|||
other.sky.rotation = sky.rotation; |
|||
other.shadow.cubemap = shadow.cubemap; |
|||
other.shadow.sunLatitude = shadow.sunLatitude; |
|||
other.shadow.sunLongitude = shadow.sunLongitude; |
|||
other.shadow.color = shadow.color; |
|||
other.name = name + " (copy)"; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute sun position to be brightest spot of the sky
|
|||
/// </summary>
|
|||
public void ResetToBrightestSpot() |
|||
=> EnvironmentElement.ResetToBrightestSpot(this); |
|||
} |
|||
|
|||
[CustomEditor(typeof(Environment))] |
|||
class EnvironmentEditor : Editor |
|||
{ |
|||
//display nothing
|
|||
public sealed override VisualElement CreateInspectorGUI() => null; |
|||
|
|||
// Don't use ImGUI
|
|||
public sealed override void OnInspectorGUI() { } |
|||
|
|||
//but make preview in Project window
|
|||
override public Texture2D RenderStaticPreview(string assetPath, UnityEngine.Object[] subAssets, int width, int height) |
|||
=> EnvironmentElement.GetLatLongThumbnailTexture(target as Environment, width); |
|||
} |
|||
|
|||
interface IBendable<T> |
|||
{ |
|||
void Bind(T data); |
|||
} |
|||
|
|||
class EnvironmentElement : VisualElement, IBendable<Environment> |
|||
{ |
|||
internal const int k_SkyThumbnailWidth = 200; |
|||
internal const int k_SkyThumbnailHeight = 100; |
|||
const int k_SkadowThumbnailWidth = 60; |
|||
const int k_SkadowThumbnailHeight = 30; |
|||
const int k_SkadowThumbnailXPosition = 130; |
|||
const int k_SkadowThumbnailYPosition = 10; |
|||
static Material s_cubeToLatlongMaterial; |
|||
static Material cubeToLatlongMaterial |
|||
{ |
|||
get |
|||
{ |
|||
if (s_cubeToLatlongMaterial == null || s_cubeToLatlongMaterial.Equals(null)) |
|||
{ |
|||
s_cubeToLatlongMaterial = new Material(Shader.Find("Hidden/LookDev/CubeToLatlong")); |
|||
} |
|||
return s_cubeToLatlongMaterial; |
|||
} |
|||
} |
|||
|
|||
VisualElement environmentParams; |
|||
Environment environment; |
|||
|
|||
Image latlong; |
|||
ObjectField skyCubemapField; |
|||
FloatField skyRotationOffset; |
|||
FloatField skyExposureField; |
|||
ObjectField shadowCubemapField; |
|||
Vector2Field sunPosition; |
|||
ColorField shadowColor; |
|||
TextField environmentName; |
|||
|
|||
Action OnChangeCallback; |
|||
|
|||
public Environment target => environment; |
|||
|
|||
public EnvironmentElement() => Create(withPreview: true); |
|||
public EnvironmentElement(bool withPreview, Action OnChangeCallback = null) |
|||
{ |
|||
this.OnChangeCallback = OnChangeCallback; |
|||
Create(withPreview); |
|||
} |
|||
|
|||
public EnvironmentElement(Environment environment) |
|||
{ |
|||
Create(withPreview: true); |
|||
Bind(environment); |
|||
} |
|||
|
|||
void Create(bool withPreview) |
|||
{ |
|||
if (withPreview) |
|||
{ |
|||
latlong = new Image(); |
|||
latlong.style.width = k_SkyThumbnailWidth; |
|||
latlong.style.height = k_SkyThumbnailHeight; |
|||
Add(latlong); |
|||
} |
|||
|
|||
environmentParams = GetDefaultInspector(); |
|||
Add(environmentParams); |
|||
} |
|||
|
|||
public void Bind(Environment environment) |
|||
{ |
|||
this.environment = environment; |
|||
if (environment == null || environment.Equals(null)) |
|||
return; |
|||
|
|||
if (latlong != null && !latlong.Equals(null)) |
|||
latlong.image = GetLatLongThumbnailTexture(); |
|||
skyCubemapField.SetValueWithoutNotify(environment.sky.cubemap); |
|||
skyRotationOffset.SetValueWithoutNotify(environment.sky.rotation); |
|||
skyExposureField.SetValueWithoutNotify(environment.sky.exposure); |
|||
shadowCubemapField.SetValueWithoutNotify(environment.shadow.cubemap); |
|||
sunPosition.SetValueWithoutNotify(new Vector2(environment.shadow.sunLongitude, environment.shadow.sunLatitude)); |
|||
shadowColor.SetValueWithoutNotify(environment.shadow.color); |
|||
environmentName.SetValueWithoutNotify(environment.name); |
|||
} |
|||
|
|||
public void Bind(Environment environment, Image deportedLatlong) |
|||
{ |
|||
latlong = deportedLatlong; |
|||
Bind(environment); |
|||
} |
|||
|
|||
static public Vector2 PositionToLatLong(Vector2 position) |
|||
{ |
|||
Vector2 result = new Vector2(); |
|||
result.x = position.y * Mathf.PI * 0.5f * Mathf.Rad2Deg; |
|||
result.y = (position.x * 0.5f + 0.5f) * 2f * Mathf.PI * Mathf.Rad2Deg; |
|||
|
|||
if (result.x < -90.0f) result.x = -90f; |
|||
if (result.x > 90.0f) result.x = 90f; |
|||
|
|||
return result; |
|||
} |
|||
|
|||
public static void ResetToBrightestSpot(Environment environment) |
|||
{ |
|||
cubeToLatlongMaterial.SetTexture("_MainTex", environment.sky.cubemap); |
|||
cubeToLatlongMaterial.SetVector("_WindowParams", new Vector4(10000, -1000.0f, 2, 0.0f)); // Neutral value to not clip
|
|||
cubeToLatlongMaterial.SetVector("_CubeToLatLongParams", new Vector4(Mathf.Deg2Rad * environment.sky.rotation, 0.5f, 1.0f, 3.0f)); // We use LOD 3 to take a region rather than a single pixel in the map
|
|||
cubeToLatlongMaterial.SetPass(0); |
|||
|
|||
int width = k_SkyThumbnailWidth; |
|||
int height = width >> 1; |
|||
|
|||
RenderTexture temporaryRT = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); |
|||
Texture2D brightestPointTexture = new Texture2D(width, height, TextureFormat.RGBAHalf, false); |
|||
|
|||
// Convert cubemap to a 2D LatLong to read on CPU
|
|||
Graphics.Blit(environment.sky.cubemap, temporaryRT, cubeToLatlongMaterial); |
|||
brightestPointTexture.ReadPixels(new Rect(0, 0, width, height), 0, 0, false); |
|||
brightestPointTexture.Apply(); |
|||
|
|||
// CPU read back
|
|||
// From Doc: The returned array is a flattened 2D array, where pixels are laid out left to right, bottom to top (i.e. row after row)
|
|||
Color[] color = brightestPointTexture.GetPixels(); |
|||
RenderTexture.active = null; |
|||
temporaryRT.Release(); |
|||
|
|||
float maxLuminance = 0.0f; |
|||
int maxIndex = 0; |
|||
for (int index = height * width - 1; index >= 0; --index) |
|||
{ |
|||
Color pixel = color[index]; |
|||
float luminance = pixel.r * 0.2126729f + pixel.g * 0.7151522f + pixel.b * 0.0721750f; |
|||
if (maxLuminance < luminance) |
|||
{ |
|||
maxLuminance = luminance; |
|||
maxIndex = index; |
|||
} |
|||
} |
|||
Vector2 sunPosition = PositionToLatLong(new Vector2(((maxIndex % width) / (float)(width - 1)) * 2f - 1f, ((maxIndex / width) / (float)(height - 1)) * 2f - 1f)); |
|||
environment.shadow.sunLatitude = sunPosition.x; |
|||
environment.shadow.sunLongitude = sunPosition.y - environment.sky.rotation; |
|||
} |
|||
|
|||
public Texture2D GetLatLongThumbnailTexture() |
|||
=> GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth); |
|||
|
|||
public static Texture2D GetLatLongThumbnailTexture(Environment environment, int width) |
|||
{ |
|||
int height = width >> 1; |
|||
RenderTexture oldActive = RenderTexture.active; |
|||
RenderTexture temporaryRT = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.sRGB); |
|||
RenderTexture.active = temporaryRT; |
|||
cubeToLatlongMaterial.SetTexture("_MainTex", environment.sky.cubemap); |
|||
cubeToLatlongMaterial.SetVector("_WindowParams", |
|||
new Vector4( |
|||
height, //height
|
|||
-1000f, //y position, -1000f to be sure to not have clipping issue (we should not clip normally but don't want to create a new shader)
|
|||
2f, //margin value
|
|||
1f)); //Pixel per Point
|
|||
cubeToLatlongMaterial.SetVector("_CubeToLatLongParams", |
|||
new Vector4( |
|||
Mathf.Deg2Rad * environment.sky.rotation, //rotation of the environment in radian
|
|||
1f, //alpha
|
|||
1f, //intensity
|
|||
0f)); //LOD
|
|||
cubeToLatlongMaterial.SetPass(0); |
|||
GL.LoadPixelMatrix(0, width, height, 0); |
|||
GL.Clear(true, true, Color.black); |
|||
Rect skyRect = new Rect(0, 0, width, height); |
|||
Renderer.DrawFullScreenQuad(skyRect); |
|||
|
|||
if (environment.shadow.cubemap != null) |
|||
{ |
|||
cubeToLatlongMaterial.SetTexture("_MainTex", environment.shadow.cubemap); |
|||
cubeToLatlongMaterial.SetVector("_WindowParams", |
|||
new Vector4( |
|||
height, //height
|
|||
-1000f, //y position, -1000f to be sure to not have clipping issue (we should not clip normally but don't want to create a new shader)
|
|||
2f, //margin value
|
|||
1f)); //Pixel per Point
|
|||
cubeToLatlongMaterial.SetVector("_CubeToLatLongParams", |
|||
new Vector4( |
|||
Mathf.Deg2Rad * environment.sky.rotation, //rotation of the environment in radian
|
|||
1f, //alpha
|
|||
0.3f, //intensity
|
|||
0f)); //LOD
|
|||
cubeToLatlongMaterial.SetPass(0); |
|||
int shadowWidth = (int)(width * (k_SkadowThumbnailWidth / (float)k_SkyThumbnailWidth)); |
|||
int shadowXPosition = (int)(width * (k_SkadowThumbnailXPosition / (float)k_SkyThumbnailWidth)); |
|||
int shadowYPosition = (int)(width * (k_SkadowThumbnailYPosition / (float)k_SkyThumbnailWidth)); |
|||
Rect shadowRect = new Rect( |
|||
shadowXPosition, |
|||
shadowYPosition, |
|||
shadowWidth, |
|||
shadowWidth >> 1); |
|||
Renderer.DrawFullScreenQuad(shadowRect); |
|||
} |
|||
|
|||
Texture2D result = new Texture2D(width, height, TextureFormat.ARGB32, false); |
|||
result.ReadPixels(new Rect(0, 0, width, height), 0, 0, false); |
|||
result.Apply(false); |
|||
RenderTexture.active = oldActive; |
|||
UnityEngine.Object.DestroyImmediate(temporaryRT); |
|||
return result; |
|||
} |
|||
|
|||
public VisualElement GetDefaultInspector() |
|||
{ |
|||
VisualElement inspector = new VisualElement() { name = "inspector" }; |
|||
|
|||
VisualElement header = new VisualElement() { name = "inspector-header" }; |
|||
header.Add(new Image() |
|||
{ |
|||
image = CoreEditorUtils.LoadIcon(@"Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/", "LookDev_EnvironmentHDR", forceLowRes: true) |
|||
}); |
|||
environmentName = new TextField(); |
|||
environmentName.isDelayed = true; |
|||
environmentName.RegisterValueChangedCallback(evt => |
|||
{ |
|||
string path = AssetDatabase.GetAssetPath(environment); |
|||
environment.name = evt.newValue; |
|||
AssetDatabase.SetLabels(environment, new string[] { evt.newValue }); |
|||
EditorUtility.SetDirty(environment); |
|||
AssetDatabase.ImportAsset(path); |
|||
environmentName.name = environment.name; |
|||
}); |
|||
header.Add(environmentName); |
|||
inspector.Add(header); |
|||
|
|||
Foldout foldout = new Foldout() |
|||
{ |
|||
text = "Environment Settings" |
|||
}; |
|||
skyCubemapField = new ObjectField("Sky with Sun") |
|||
{ |
|||
tooltip = "A cubemap that will be used as the sky." |
|||
}; |
|||
skyCubemapField.allowSceneObjects = false; |
|||
skyCubemapField.objectType = typeof(Cubemap); |
|||
skyCubemapField.RegisterValueChangedCallback(evt => |
|||
{ |
|||
var tmp = environment.sky.cubemap; |
|||
RegisterChange(ref tmp, evt.newValue as Cubemap); |
|||
environment.sky.cubemap = tmp; |
|||
latlong.image = GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth); |
|||
}); |
|||
foldout.Add(skyCubemapField); |
|||
|
|||
shadowCubemapField = new ObjectField("Sky without Sun") |
|||
{ |
|||
tooltip = "[Optional] A cubemap that will be used to compute self shadowing.\nIt should be the same sky without the sun.\nIf nothing is provided, the sky with sun will be used with lower intensity." |
|||
}; |
|||
shadowCubemapField.allowSceneObjects = false; |
|||
shadowCubemapField.objectType = typeof(Cubemap); |
|||
shadowCubemapField.RegisterValueChangedCallback(evt => |
|||
{ |
|||
var tmp = environment.shadow.cubemap; |
|||
RegisterChange(ref tmp, evt.newValue as Cubemap); |
|||
environment.shadow.cubemap = tmp; |
|||
latlong.image = GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth); |
|||
}); |
|||
foldout.Add(shadowCubemapField); |
|||
|
|||
skyRotationOffset = new FloatField("Rotation") |
|||
{ |
|||
tooltip = "Rotation offset on the longitude of the sky." |
|||
}; |
|||
skyRotationOffset.RegisterValueChangedCallback(evt |
|||
=> RegisterChange(ref environment.sky.rotation, Environment.Shadow.ClampLongitude(evt.newValue), skyRotationOffset, updatePreview: true)); |
|||
foldout.Add(skyRotationOffset); |
|||
|
|||
skyExposureField = new FloatField("Exposure") |
|||
{ |
|||
tooltip = "The exposure to apply with this sky." |
|||
}; |
|||
skyExposureField.RegisterValueChangedCallback(evt |
|||
=> RegisterChange(ref environment.sky.exposure, evt.newValue)); |
|||
foldout.Add(skyExposureField); |
|||
var style = foldout.Q<Toggle>().style; |
|||
style.marginLeft = 3; |
|||
style.unityFontStyleAndWeight = FontStyle.Bold; |
|||
inspector.Add(foldout); |
|||
|
|||
sunPosition = new Vector2Field("Sun Position") |
|||
{ |
|||
tooltip = "The sun position as (Longitude, Latitude)\nThe button compute brightest position in the sky with sun." |
|||
}; |
|||
sunPosition.Q("unity-x-input").Q<FloatField>().formatString = "n1"; |
|||
sunPosition.Q("unity-y-input").Q<FloatField>().formatString = "n1"; |
|||
sunPosition.RegisterValueChangedCallback(evt => |
|||
{ |
|||
var tmpContainer = new Vector2( |
|||
environment.shadow.sunLongitude, |
|||
environment.shadow.sunLatitude); |
|||
var tmpNewValue = new Vector2( |
|||
Environment.Shadow.ClampLongitude(evt.newValue.x), |
|||
Environment.Shadow.ClampLatitude(evt.newValue.y)); |
|||
RegisterChange(ref tmpContainer, tmpNewValue, sunPosition); |
|||
environment.shadow.sunLongitude = tmpContainer.x; |
|||
environment.shadow.sunLatitude = tmpContainer.y; |
|||
}); |
|||
foldout.Add(sunPosition); |
|||
|
|||
Button sunToBrightess = new Button(() => |
|||
{ |
|||
ResetToBrightestSpot(environment); |
|||
sunPosition.SetValueWithoutNotify(new Vector2( |
|||
Environment.Shadow.ClampLongitude(environment.shadow.sunLongitude), |
|||
Environment.Shadow.ClampLatitude(environment.shadow.sunLatitude))); |
|||
}) |
|||
{ |
|||
name = "sunToBrightestButton" |
|||
}; |
|||
sunToBrightess.Add(new Image() |
|||
{ |
|||
image = CoreEditorUtils.LoadIcon(@"Packages/com.unity.render-pipelines.core/Editor/LookDev/Icons/", "LookDev_SunPosition", forceLowRes: true) |
|||
}); |
|||
sunToBrightess.AddToClassList("sun-to-brightest-button"); |
|||
var vector2Input = sunPosition.Q(className: "unity-vector2-field__input"); |
|||
vector2Input.Remove(sunPosition.Q(className: "unity-composite-field__field-spacer")); |
|||
vector2Input.Add(sunToBrightess); |
|||
|
|||
shadowColor = new ColorField("Shadow Tint") |
|||
{ |
|||
tooltip = "The wanted shadow tint to be used when computing shadow." |
|||
}; |
|||
shadowColor.RegisterValueChangedCallback(evt |
|||
=> RegisterChange(ref environment.shadow.color, evt.newValue)); |
|||
foldout.Add(shadowColor); |
|||
|
|||
style = foldout.Q<Toggle>().style; |
|||
style.marginLeft = 3; |
|||
style.unityFontStyleAndWeight = FontStyle.Bold; |
|||
inspector.Add(foldout); |
|||
|
|||
return inspector; |
|||
} |
|||
|
|||
void RegisterChange<TValueType>(ref TValueType reflectedVariable, TValueType newValue, BaseField<TValueType> resyncField = null, bool updatePreview = false) |
|||
{ |
|||
if (environment == null || environment.Equals(null)) |
|||
return; |
|||
reflectedVariable = newValue; |
|||
resyncField?.SetValueWithoutNotify(newValue); |
|||
if (updatePreview && latlong != null && !latlong.Equals(null)) |
|||
latlong.image = GetLatLongThumbnailTexture(environment, k_SkyThumbnailWidth); |
|||
EditorUtility.SetDirty(environment); |
|||
OnChangeCallback?.Invoke(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 612aa45d38dd94a479185b83f080aa22 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
using UnityEngine.Rendering; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using UnityEngine.UIElements; |
|||
using System.Linq; |
|||
using System.IO; |
|||
using System.Linq.Expressions; |
|||
using System.Reflection; |
|||
|
|||
namespace UnityEditor.Rendering.LookDev |
|||
{ |
|||
/// <summary>
|
|||
/// Class containing a collection of Environment
|
|||
/// </summary>
|
|||
public class EnvironmentLibrary : BaseEnvironmentLibrary |
|||
{ |
|||
[field: SerializeField] |
|||
List<Environment> environments { get; set; } = new List<Environment>(); |
|||
|
|||
/// <summary>
|
|||
/// Number of elements in the collection
|
|||
/// </summary>
|
|||
public int Count => environments.Count; |
|||
/// <summary>
|
|||
/// Indexer giving access to contained Environment
|
|||
/// </summary>
|
|||
public Environment this[int index] => environments[index]; |
|||
|
|||
/// <summary>
|
|||
/// Create a new empty Environment at the end of the collection
|
|||
/// </summary>
|
|||
/// <returns>The created Environment</returns>
|
|||
public Environment Add() |
|||
{ |
|||
Environment environment = ScriptableObject.CreateInstance<Environment>(); |
|||
environment.name = "New Environment"; |
|||
Undo.RegisterCreatedObjectUndo(environment, "Add Environment"); |
|||
|
|||
environments.Add(environment); |
|||
|
|||
// Store this new environment as a subasset so we can reference it safely afterwards.
|
|||
AssetDatabase.AddObjectToAsset(environment, this); |
|||
|
|||
// Force save / refresh. Important to do this last because SaveAssets can cause effect to become null!
|
|||
EditorUtility.SetDirty(this); |
|||
AssetDatabase.SaveAssets(); |
|||
|
|||
return environment; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Remove Environment of the collection at given index
|
|||
/// </summary>
|
|||
/// <param name="index">Index where to remove Environment</param>
|
|||
public void Remove(int index) |
|||
{ |
|||
Environment environment = environments[index]; |
|||
Undo.RecordObject(this, "Remove Environment"); |
|||
environments.RemoveAt(index); |
|||
Undo.DestroyObjectImmediate(environment); |
|||
|
|||
// Force save / refresh
|
|||
EditorUtility.SetDirty(this); |
|||
AssetDatabase.SaveAssets(); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Duplicate the Environment at given index and add it at the end of the Collection
|
|||
/// </summary>
|
|||
/// <param name="fromIndex">Index where to take data for duplication</param>
|
|||
/// <returns>The created Environment</returns>
|
|||
public Environment Duplicate(int fromIndex) |
|||
{ |
|||
Environment environment = ScriptableObject.CreateInstance<Environment>(); |
|||
Environment environmentToCopy = environments[fromIndex]; |
|||
environmentToCopy.CopyTo(environment); |
|||
|
|||
Undo.RegisterCreatedObjectUndo(environment, "Duplicate Environment"); |
|||
|
|||
environments.Add(environment); |
|||
|
|||
// Store this new environment as a subasset so we can reference it safely afterwards.
|
|||
AssetDatabase.AddObjectToAsset(environment, this); |
|||
|
|||
// Force save / refresh. Important to do this last because SaveAssets can cause effect to become null!
|
|||
EditorUtility.SetDirty(this); |
|||
AssetDatabase.SaveAssets(); |
|||
|
|||
return environment; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Compute position of given Environment in the collection
|
|||
/// </summary>
|
|||
/// <param name="environment">Environment to look at</param>
|
|||
/// <returns>Index of the searched environment. If not found, -1.</returns>
|
|||
public int IndexOf(Environment environment) |
|||
=> environments.IndexOf(environment); |
|||
} |
|||
|
|||
[CustomEditor(typeof(EnvironmentLibrary))] |
|||
class EnvironmentLibraryEditor : Editor |
|||
{ |
|||
|
|||
VisualElement root; |
|||
|
|||
public sealed override VisualElement CreateInspectorGUI() |
|||
{ |
|||
var library = target as EnvironmentLibrary; |
|||
root = new VisualElement(); |
|||
|
|||
Button open = new Button(() => |
|||
{ |
|||
if (!LookDev.open) |
|||
LookDev.Open(); |
|||
LookDev.currentContext.UpdateEnvironmentLibrary(library); |
|||
LookDev.currentEnvironmentDisplayer.Repaint(); |
|||
}) |
|||
{ |
|||
text = "Open in LookDev window" |
|||
}; |
|||
|
|||
root.Add(open); |
|||
return root; |
|||
} |
|||
|
|||
// Don't use ImGUI
|
|||
public sealed override void OnInspectorGUI() { } |
|||
} |
|||
|
|||
class EnvironmentLibraryCreator : ProjectWindowCallback.EndNameEditAction |
|||
{ |
|||
public override void Action(int instanceId, string pathName, string resourceFile) |
|||
{ |
|||
var newAsset = CreateInstance<EnvironmentLibrary>(); |
|||
newAsset.name = Path.GetFileName(pathName); |
|||
AssetDatabase.CreateAsset(newAsset, pathName); |
|||
ProjectWindowUtil.ShowCreatedAsset(newAsset); |
|||
} |
|||
|
|||
[MenuItem("Assets/Create/LookDev/Environment Library", priority = 2000)] |
|||
public static void Create() |
|||
{ |
|||
var icon = EditorGUIUtility.FindTexture("ScriptableObject Icon"); |
|||
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, ScriptableObject.CreateInstance<EnvironmentLibraryCreator>(), "EnvironmentLibrary.asset", icon, null); |
|||
} |
|||
} |
|||
|
|||
static class EnvironmentLibraryLoader |
|||
{ |
|||
public static void Load(Action onInspectorRedrawRequested) |
|||
{ |
|||
UnityEngine.Object target = LookDev.currentContext.environmentLibrary; |
|||
UIElementObjectSelectorWorkaround.Show(target, typeof(EnvironmentLibrary), LoadCallback(onInspectorRedrawRequested)); |
|||
} |
|||
|
|||
static Action<UnityEngine.Object> LoadCallback(Action onUpdate) |
|||
{ |
|||
return (UnityEngine.Object newLibrary) => |
|||
{ |
|||
LookDev.currentContext.UpdateEnvironmentLibrary(newLibrary as EnvironmentLibrary); |
|||
onUpdate?.Invoke(); |
|||
}; |
|||
} |
|||
|
|||
|
|||
// As in UIElement.ObjectField we cannot support cancel when closing window
|
|||
static class UIElementObjectSelectorWorkaround |
|||
{ |
|||
static Action<UnityEngine.Object, Type, Action<UnityEngine.Object>> ShowObjectSelector; |
|||
|
|||
static UIElementObjectSelectorWorkaround() |
|||
{ |
|||
Type playerSettingsType = typeof(PlayerSettings); |
|||
Type objectSelectorType = playerSettingsType.Assembly.GetType("UnityEditor.ObjectSelector"); |
|||
var instanceObjectSelectorInfo = objectSelectorType.GetProperty("get", BindingFlags.Static | BindingFlags.Public); |
|||
var showInfo = objectSelectorType.GetMethod("Show", BindingFlags.NonPublic | BindingFlags.Instance, null, new[] { typeof(UnityEngine.Object), typeof(Type), typeof(SerializedProperty), typeof(bool), typeof(List<int>), typeof(Action<UnityEngine.Object>), typeof(Action<UnityEngine.Object>) }, null); |
|||
var objectSelectorVariable = Expression.Variable(objectSelectorType, "objectSelector"); |
|||
var objectParameter = Expression.Parameter(typeof(UnityEngine.Object), "unityObject"); |
|||
var typeParameter = Expression.Parameter(typeof(Type), "type"); |
|||
var onChangedObjectParameter = Expression.Parameter(typeof(Action<UnityEngine.Object>), "onChangedObject"); |
|||
var showObjectSelectorBlock = Expression.Block( |
|||
new[] { objectSelectorVariable }, |
|||
Expression.Assign(objectSelectorVariable, Expression.Call(null, instanceObjectSelectorInfo.GetGetMethod())), |
|||
Expression.Call(objectSelectorVariable, showInfo, objectParameter, typeParameter, Expression.Constant(null, typeof(SerializedProperty)), Expression.Constant(false), Expression.Constant(null, typeof(List<int>)), Expression.Constant(null, typeof(Action<UnityEngine.Object>)), onChangedObjectParameter) |
|||
); |
|||
var showObjectSelectorLambda = Expression.Lambda<Action<UnityEngine.Object, Type, Action<UnityEngine.Object>>>(showObjectSelectorBlock, objectParameter, typeParameter, onChangedObjectParameter); |
|||
ShowObjectSelector = showObjectSelectorLambda.Compile(); |
|||
} |
|||
|
|||
public static void Show(UnityEngine.Object obj, Type type, Action<UnityEngine.Object> onObjectChanged) |
|||
{ |
|||
ShowObjectSelector(obj, type, onObjectChanged); |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 985c12cb5f71c6440825869433552460 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 9d38fc3327537ec418b043933457c849 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 3092b2985cafe0b43b1f02ce1d6829d5 |
|||
timeCreated: 1470993508 |
|||
licenseType: Pro |
|||
TextureImporter: |
|||
fileIDToRecycleName: {} |
|||
externalObjects: {} |
|||
serializedVersion: 5 |
|||
mipmaps: |
|||
mipMapMode: 0 |
|||
enableMipMap: 0 |
|||
sRGBTexture: 1 |
|||
linearTexture: 0 |
|||
fadeOut: 0 |
|||
borderMipMap: 0 |
|||
mipMapsPreserveCoverage: 0 |
|||
alphaTestReferenceValue: 0.5 |
|||
mipMapFadeDistanceStart: 1 |
|||
mipMapFadeDistanceEnd: 3 |
|||
bumpmap: |
|||
convertToNormalMap: 0 |
|||
externalNormalMap: 0 |
|||
heightScale: 0.25 |
|||
normalMapFilter: 0 |
|||
isReadable: 0 |
|||
grayScaleToAlpha: 0 |
|||
generateCubemap: 6 |
|||
cubemapConvolution: 0 |
|||
seamlessCubemap: 0 |
|||
textureFormat: 1 |
|||
maxTextureSize: 2048 |
|||
textureSettings: |
|||
serializedVersion: 2 |
|||
filterMode: 0 |
|||
aniso: -1 |
|||
mipBias: 0 |
|||
wrapU: 1 |
|||
wrapV: 1 |
|||
wrapW: 1 |
|||
nPOTScale: 0 |
|||
lightmap: 0 |
|||
compressionQuality: 50 |
|||
spriteMode: 0 |
|||
spriteExtrude: 1 |
|||
spriteMeshType: 1 |
|||
alignment: 0 |
|||
spritePivot: {x: 0.5, y: 0.5} |
|||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} |
|||
spritePixelsToUnits: 100 |
|||
alphaUsage: 1 |
|||
alphaIsTransparency: 0 |
|||
spriteTessellationDetail: -1 |
|||
textureType: 0 |
|||
textureShape: 1 |
|||
maxTextureSizeSet: 0 |
|||
compressionQualitySet: 0 |
|||
textureFormatSet: 0 |
|||
platformSettings: |
|||
- buildTarget: DefaultTexturePlatform |
|||
maxTextureSize: 2048 |
|||
resizeAlgorithm: 0 |
|||
textureFormat: -1 |
|||
textureCompression: 0 |
|||
compressionQuality: 50 |
|||
crunchedCompression: 0 |
|||
allowsAlphaSplitting: 0 |
|||
overridden: 0 |
|||
androidETC2FallbackOverride: 0 |
|||
spriteSheet: |
|||
serializedVersion: 2 |
|||
sprites: [] |
|||
outline: [] |
|||
physicsShape: [] |
|||
spritePackingTag: |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 38e497c7c3ba97b42aee0fc8cec4178b |
|||
timeCreated: 1470993508 |
|||
licenseType: Pro |
|||
TextureImporter: |
|||
fileIDToRecycleName: {} |
|||
externalObjects: {} |
|||
serializedVersion: 5 |
|||
mipmaps: |
|||
mipMapMode: 0 |
|||
enableMipMap: 0 |
|||
sRGBTexture: 1 |
|||
linearTexture: 0 |
|||
fadeOut: 0 |
|||
borderMipMap: 0 |
|||
mipMapsPreserveCoverage: 0 |
|||
alphaTestReferenceValue: 0.5 |
|||
mipMapFadeDistanceStart: 1 |
|||
mipMapFadeDistanceEnd: 3 |
|||
bumpmap: |
|||
convertToNormalMap: 0 |
|||
externalNormalMap: 0 |
|||
heightScale: 0.25 |
|||
normalMapFilter: 0 |
|||
isReadable: 0 |
|||
grayScaleToAlpha: 0 |
|||
generateCubemap: 6 |
|||
cubemapConvolution: 0 |
|||
seamlessCubemap: 0 |
|||
textureFormat: 1 |
|||
maxTextureSize: 2048 |
|||
textureSettings: |
|||
serializedVersion: 2 |
|||
filterMode: 0 |
|||
aniso: -1 |
|||
mipBias: 0 |
|||
wrapU: 1 |
|||
wrapV: 1 |
|||
wrapW: 1 |
|||
nPOTScale: 0 |
|||
lightmap: 0 |
|||
compressionQuality: 50 |
|||
spriteMode: 0 |
|||
spriteExtrude: 1 |
|||
spriteMeshType: 1 |
|||
alignment: 0 |
|||
spritePivot: {x: 0.5, y: 0.5} |
|||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} |
|||
spritePixelsToUnits: 100 |
|||
alphaUsage: 1 |
|||
alphaIsTransparency: 0 |
|||
spriteTessellationDetail: -1 |
|||
textureType: 0 |
|||
textureShape: 1 |
|||
maxTextureSizeSet: 0 |
|||
compressionQualitySet: 0 |
|||
textureFormatSet: 0 |
|||
platformSettings: |
|||
- buildTarget: DefaultTexturePlatform |
|||
maxTextureSize: 2048 |
|||
resizeAlgorithm: 0 |
|||
textureFormat: -1 |
|||
textureCompression: 0 |
|||
compressionQuality: 50 |
|||
crunchedCompression: 0 |
|||
allowsAlphaSplitting: 0 |
|||
overridden: 0 |
|||
androidETC2FallbackOverride: 0 |
|||
spriteSheet: |
|||
serializedVersion: 2 |
|||
sprites: [] |
|||
outline: [] |
|||
physicsShape: [] |
|||
spritePackingTag: |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: de30227a4b9409e499c278d82bfa458a |
|||
timeCreated: 1470994216 |
|||
licenseType: Pro |
|||
TextureImporter: |
|||
fileIDToRecycleName: {} |
|||
externalObjects: {} |
|||
serializedVersion: 5 |
|||
mipmaps: |
|||
mipMapMode: 0 |
|||
enableMipMap: 0 |
|||
sRGBTexture: 1 |
|||
linearTexture: 0 |
|||
fadeOut: 0 |
|||
borderMipMap: 0 |
|||
mipMapsPreserveCoverage: 0 |
|||
alphaTestReferenceValue: 0.5 |
|||
mipMapFadeDistanceStart: 1 |
|||
mipMapFadeDistanceEnd: 3 |
|||
bumpmap: |
|||
convertToNormalMap: 0 |
|||
externalNormalMap: 0 |
|||
heightScale: 0.25 |
|||
normalMapFilter: 0 |
|||
isReadable: 0 |
|||
grayScaleToAlpha: 0 |
|||
generateCubemap: 6 |
|||
cubemapConvolution: 0 |
|||
seamlessCubemap: 0 |
|||
textureFormat: 1 |
|||
maxTextureSize: 2048 |
|||
textureSettings: |
|||
serializedVersion: 2 |
|||
filterMode: 0 |
|||
aniso: -1 |
|||
mipBias: 0 |
|||
wrapU: 1 |
|||
wrapV: 1 |
|||
wrapW: 1 |
|||
nPOTScale: 0 |
|||
lightmap: 0 |
|||
compressionQuality: 50 |
|||
spriteMode: 0 |
|||
spriteExtrude: 1 |
|||
spriteMeshType: 1 |
|||
alignment: 0 |
|||
spritePivot: {x: 0.5, y: 0.5} |
|||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} |
|||
spritePixelsToUnits: 100 |
|||
alphaUsage: 1 |
|||
alphaIsTransparency: 0 |
|||
spriteTessellationDetail: -1 |
|||
textureType: 0 |
|||
textureShape: 1 |
|||
maxTextureSizeSet: 0 |
|||
compressionQualitySet: 0 |
|||
textureFormatSet: 0 |
|||
platformSettings: |
|||
- buildTarget: DefaultTexturePlatform |
|||
maxTextureSize: 2048 |
|||
resizeAlgorithm: 0 |
|||
textureFormat: -1 |
|||
textureCompression: 0 |
|||
compressionQuality: 50 |
|||
crunchedCompression: 0 |
|||
allowsAlphaSplitting: 0 |
|||
overridden: 0 |
|||
androidETC2FallbackOverride: 0 |
|||
spriteSheet: |
|||
serializedVersion: 2 |
|||
sprites: [] |
|||
outline: [] |
|||
physicsShape: [] |
|||
spritePackingTag: |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 6b9dda8129e93a040989f49b03f6f65a |
|||
timeCreated: 1470994216 |
|||
licenseType: Pro |
|||
TextureImporter: |
|||
fileIDToRecycleName: {} |
|||
externalObjects: {} |
|||
serializedVersion: 5 |
|||
mipmaps: |
|||
mipMapMode: 0 |
|||
enableMipMap: 0 |
|||
sRGBTexture: 1 |
|||
linearTexture: 0 |
|||
fadeOut: 0 |
|||
borderMipMap: 0 |
|||
mipMapsPreserveCoverage: 0 |
|||
alphaTestReferenceValue: 0.5 |
|||
mipMapFadeDistanceStart: 1 |
|||
mipMapFadeDistanceEnd: 3 |
|||
bumpmap: |
|||
convertToNormalMap: 0 |
|||
externalNormalMap: 0 |
|||
heightScale: 0.25 |
|||
normalMapFilter: 0 |
|||
isReadable: 0 |
|||
grayScaleToAlpha: 0 |
|||
generateCubemap: 6 |
|||
cubemapConvolution: 0 |
|||
seamlessCubemap: 0 |
|||
textureFormat: 1 |
|||
maxTextureSize: 2048 |
|||
textureSettings: |
|||
serializedVersion: 2 |
|||
filterMode: 0 |
|||
aniso: -1 |
|||
mipBias: 0 |
|||
wrapU: 1 |
|||
wrapV: 1 |
|||
wrapW: 1 |
|||
nPOTScale: 0 |
|||
lightmap: 0 |
|||
compressionQuality: 50 |
|||
spriteMode: 0 |
|||
spriteExtrude: 1 |
|||
spriteMeshType: 1 |
|||
alignment: 0 |
|||
spritePivot: {x: 0.5, y: 0.5} |
|||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} |
|||
spritePixelsToUnits: 100 |
|||
alphaUsage: 1 |
|||
alphaIsTransparency: 0 |
|||
spriteTessellationDetail: -1 |
|||
textureType: 0 |
|||
textureShape: 1 |
|||
maxTextureSizeSet: 0 |
|||
compressionQualitySet: 0 |
|||
textureFormatSet: 0 |
|||
platformSettings: |
|||
- buildTarget: DefaultTexturePlatform |
|||
maxTextureSize: 2048 |
|||
resizeAlgorithm: 0 |
|||
textureFormat: -1 |
|||
textureCompression: 0 |
|||
compressionQuality: 50 |
|||
crunchedCompression: 0 |
|||
allowsAlphaSplitting: 0 |
|||
overridden: 0 |
|||
androidETC2FallbackOverride: 0 |
|||
spriteSheet: |
|||
serializedVersion: 2 |
|||
sprites: [] |
|||
outline: [] |
|||
physicsShape: [] |
|||
spritePackingTag: |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 2ee66e81975377745aaa59fe11ad374d |
|||
timeCreated: 1471001145 |
|||
licenseType: Pro |
|||
TextureImporter: |
|||
fileIDToRecycleName: {} |
|||
externalObjects: {} |
|||
serializedVersion: 5 |
|||
mipmaps: |
|||
mipMapMode: 0 |
|||
enableMipMap: 0 |
|||
sRGBTexture: 1 |
|||
linearTexture: 1 |
|||
fadeOut: 0 |
|||
borderMipMap: 0 |
|||
mipMapsPreserveCoverage: 0 |
|||
alphaTestReferenceValue: 0.5 |
|||
mipMapFadeDistanceStart: 1 |
|||
mipMapFadeDistanceEnd: 3 |
|||
bumpmap: |
|||
convertToNormalMap: 0 |
|||
externalNormalMap: 0 |
|||
heightScale: 0.25 |
|||
normalMapFilter: 0 |
|||
isReadable: 0 |
|||
grayScaleToAlpha: 0 |
|||
generateCubemap: 6 |
|||
cubemapConvolution: 0 |
|||
seamlessCubemap: 0 |
|||
textureFormat: 5 |
|||
maxTextureSize: 2048 |
|||
textureSettings: |
|||
serializedVersion: 2 |
|||
filterMode: 0 |
|||
aniso: -1 |
|||
mipBias: 0 |
|||
wrapU: 1 |
|||
wrapV: 1 |
|||
wrapW: 1 |
|||
nPOTScale: 0 |
|||
lightmap: 0 |
|||
compressionQuality: 50 |
|||
spriteMode: 0 |
|||
spriteExtrude: 1 |
|||
spriteMeshType: 1 |
|||
alignment: 0 |
|||
spritePivot: {x: 0.5, y: 0.5} |
|||
spriteBorder: {x: 0, y: 0, z: 0, w: 0} |
|||
spritePixelsToUnits: 100 |
|||
alphaUsage: 1 |
|||
alphaIsTransparency: 0 |
|||
spriteTessellationDetail: -1 |
|||
textureType: 0 |
|||
textureShape: 1 |
|||
maxTextureSizeSet: 0 |
|||
compressionQualitySet: 0 |
|||
textureFormatSet: 0 |
|||
platformSettings: |
|||
- buildTarget: DefaultTexturePlatform |
|||
maxTextureSize: 2048 |
|||
resizeAlgorithm: 0 |
|||
textureFormat: -1 |
|||
textureCompression: 0 |
|||
compressionQuality: 50 |
|||
crunchedCompression: 0 |
|||
allowsAlphaSplitting: 0 |
|||
overridden: 0 |
|||
androidETC2FallbackOverride: 0 |
|||
spriteSheet: |
|||
serializedVersion: 2 |
|||
sprites: [] |
|||
outline: [] |
|||
physicsShape: [] |
|||
spritePackingTag: |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
部分文件因为文件数量过多而无法显示
撰写
预览
正在加载...
取消
保存
Reference in new issue