Skip to content

Commit 1395482

Browse files
Merge pull request #193 from Agent-Healm/main
Adds PreviewMesh attribute to Tri-Inspector along with sample demonstrating parameter usages.
2 parents 1921fb0 + cf7b705 commit 1395482

File tree

9 files changed

+409
-0
lines changed

9 files changed

+409
-0
lines changed
Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
using System;
2+
using TriInspector;
3+
using TriInspector.Drawers;
4+
using TriInspector.Elements;
5+
using TriInspector.Utilities;
6+
using UnityEditor;
7+
using UnityEngine;
8+
using Object = UnityEngine.Object;
9+
10+
[assembly: RegisterTriAttributeDrawer(typeof(PreviewMeshDrawer), TriDrawerOrder.Decorator,
11+
ApplyOnArrayElement = true)]
12+
13+
namespace TriInspector.Drawers
14+
{
15+
public class PreviewMeshDrawer : TriAttributeDrawer<PreviewMeshAttribute>
16+
{
17+
private class PreviewMeshPicker : TriElement
18+
{
19+
private readonly TriProperty _property;
20+
private readonly bool _useFoldout;
21+
22+
public PreviewMeshPicker(TriProperty property, bool useFoldout)
23+
{
24+
_property = property;
25+
_useFoldout = useFoldout;
26+
}
27+
28+
public override float GetHeight(float width)
29+
{
30+
return EditorGUIUtility.singleLineHeight;
31+
}
32+
public override void OnGUI(Rect position)
33+
{
34+
var pickerRect = position;
35+
GUIContent label = new(_property.DisplayName);
36+
37+
if (_useFoldout)
38+
{
39+
var prefixRect = new Rect(position)
40+
{
41+
height = EditorGUIUtility.singleLineHeight,
42+
xMax = position.xMin + EditorGUIUtility.labelWidth,
43+
};
44+
pickerRect = new Rect(position)
45+
{
46+
height = EditorGUIUtility.singleLineHeight,
47+
xMin = prefixRect.xMax,
48+
};
49+
50+
TriEditorGUI.Foldout(prefixRect, _property);
51+
label = GUIContent.none;
52+
}
53+
54+
EditorGUI.BeginChangeCheck();
55+
Object obj = _property.Value as Object;
56+
var asset = EditorGUI.ObjectField(pickerRect, label, obj, typeof(GameObject), true);
57+
if (EditorGUI.EndChangeCheck())
58+
{
59+
_property.SetValue(asset);
60+
}
61+
}
62+
}
63+
64+
private class PreviewMesh : TriElement
65+
{
66+
private readonly int _height;
67+
private readonly int _width;
68+
private readonly TriProperty _property;
69+
private readonly bool _useFoldout;
70+
private readonly PreviewMeshRotationMethod _rotationMethod;
71+
72+
private PreviewRenderUtility _previewUtility;
73+
private static Material _mat;
74+
private Material GetMat
75+
{
76+
get
77+
{
78+
if (_mat == null)
79+
{
80+
_mat = new Material(Shader.Find("Universal Render Pipeline/Lit"));
81+
_mat.color = new Color(0.4f, 0.7f, 0.4f);
82+
}
83+
return _mat;
84+
}
85+
}
86+
87+
private readonly float _c_ROTATION_SENSITIVITY = -0.5f;
88+
private readonly float _c_ZOOM_SENSITIVITY = 0.1f;
89+
private readonly float _c_ZOOM_SENSITIVITY_MIN = 2f;
90+
private readonly float _c_ZOOM_SENSITIVITY_MAX = 10f;
91+
private readonly float _c_MIN_WIDTH = 50f;
92+
private readonly float _c_DEFAULT_CAMERA_DISTANCE = 4f;
93+
94+
private Quaternion _previewQuaternion;
95+
private Mesh _sharedMesh;
96+
private float _distance;
97+
98+
private Vector2 _previewDir = new(-20f, 0f);
99+
100+
#region Initialization
101+
public PreviewMesh(TriProperty property, int size, int width, bool useFoldout, PreviewMeshRotationMethod rotationMethod)
102+
{
103+
_property = property;
104+
_height = size;
105+
_width = width;
106+
_useFoldout = useFoldout;
107+
_rotationMethod = rotationMethod;
108+
}
109+
110+
protected override void OnAttachToPanel()
111+
{
112+
_previewUtility = new();
113+
_property.ValueChanged += OnValueChanged;
114+
115+
// Setup lights
116+
_previewUtility.lights[0].intensity = 1.3f;
117+
_previewUtility.lights[0].transform.rotation = Quaternion.Euler(40f, 40f, 0);
118+
_previewUtility.lights[1].intensity = 1.3f;
119+
120+
// Setup camera
121+
_previewUtility.cameraFieldOfView = 30f;
122+
_previewUtility.camera.nearClipPlane = 0.1f;
123+
_previewUtility.camera.farClipPlane = 100f;
124+
_previewUtility.camera.backgroundColor = Color.black;
125+
_previewUtility.camera.clearFlags = CameraClearFlags.Color;
126+
127+
base.OnAttachToPanel();
128+
129+
GetMeshObject();
130+
}
131+
132+
protected override void OnDetachFromPanel()
133+
{
134+
_previewUtility.Cleanup();
135+
_previewUtility = null;
136+
137+
_property.ValueChanged -= OnValueChanged;
138+
139+
base.OnDetachFromPanel();
140+
}
141+
142+
private void OnValueChanged(TriProperty property)
143+
{
144+
GetMeshObject();
145+
}
146+
147+
public override float GetHeight(float width)
148+
{
149+
if (_sharedMesh == null)
150+
{
151+
return 0f;
152+
}
153+
if (!_useFoldout || _property.IsExpanded)
154+
{
155+
return _height;
156+
}
157+
return 0f;
158+
}
159+
160+
public override void OnGUI(Rect position)
161+
{
162+
if (_sharedMesh == null)
163+
{
164+
return;
165+
}
166+
float currentWidth = _width == -1 ? (int) position.width : _width;
167+
currentWidth = Math.Max(currentWidth, _c_MIN_WIDTH);
168+
169+
if (position.height == 0f)
170+
{
171+
return;
172+
}
173+
174+
position = new Rect(position.x, position.y, currentWidth, _height);
175+
_previewUtility.BeginPreview(position, GUIStyle.none);
176+
_previewUtility.DrawMesh(_sharedMesh, Matrix4x4.TRS(Vector3.zero, _previewQuaternion, Vector3.one), GetMat, 0);
177+
_previewUtility.camera.Render();
178+
179+
Texture result = _previewUtility.EndPreview();
180+
181+
if (result)
182+
{
183+
GUI.DrawTexture(position, result, ScaleMode.ScaleToFit, false);
184+
}
185+
if (position.Contains(Event.current.mousePosition))
186+
{
187+
HandleMouseEvent(Event.current);
188+
}
189+
}
190+
191+
#endregion
192+
193+
194+
#region Helper Function
195+
196+
private void GetMeshObject()
197+
{
198+
var obj = _property.Value as Object;
199+
200+
if (obj == null)
201+
{
202+
_sharedMesh = null;
203+
return;
204+
}
205+
206+
_previewQuaternion = Quaternion.Euler(_previewDir);
207+
_distance = _c_DEFAULT_CAMERA_DISTANCE;
208+
209+
// Draw supported types
210+
if (obj is Mesh mesh)
211+
{
212+
_sharedMesh = mesh;
213+
}
214+
else if (obj is GameObject go)
215+
{
216+
var mf = go.GetComponentInChildren<MeshFilter>();
217+
if (mf != null)
218+
{
219+
_sharedMesh = mf.sharedMesh;
220+
}
221+
else
222+
{
223+
Debug.Log("No MeshFilter found on GameObject.");
224+
}
225+
}
226+
227+
if (_sharedMesh != null)
228+
{
229+
UpdatePreviewCamera();
230+
}
231+
}
232+
233+
private void UpdatePreviewCamera()
234+
{
235+
var bounds = _sharedMesh.bounds;
236+
237+
// fallback bounds
238+
if (bounds.size == Vector3.zero)
239+
{
240+
bounds = new Bounds(Vector3.zero, Vector3.one);
241+
}
242+
243+
var magnitude = bounds.extents.magnitude;
244+
_previewUtility.camera.transform.position = bounds.center + Vector3.back * (magnitude * _distance);
245+
_previewUtility.camera.transform.LookAt(bounds.center);
246+
}
247+
248+
private void HandleMouseEvent(Event mouseEvent)
249+
{
250+
var shift = mouseEvent.shift;
251+
252+
switch (mouseEvent.type)
253+
{
254+
case EventType.MouseDrag:
255+
var cameraMovement = mouseEvent.delta * _c_ROTATION_SENSITIVITY;
256+
HandlePreviewCameraRotation(cameraMovement);
257+
mouseEvent.Use();
258+
break;
259+
260+
case EventType.ScrollWheel:
261+
_distance = Mathf.Clamp(_distance + mouseEvent.delta.x * _c_ZOOM_SENSITIVITY, _c_ZOOM_SENSITIVITY_MIN, _c_ZOOM_SENSITIVITY_MAX);
262+
if (shift)
263+
{
264+
UpdatePreviewCamera();
265+
mouseEvent.Use();
266+
}
267+
break;
268+
269+
default:
270+
return;
271+
}
272+
}
273+
274+
private void HandlePreviewCameraRotation(Vector2 movement)
275+
{
276+
float pitch = movement.y;
277+
float yaw = movement.x;
278+
279+
switch (_rotationMethod)
280+
{
281+
case PreviewMeshRotationMethod.Clamped:
282+
_previewDir.x = Mathf.Clamp(pitch + _previewDir.x, -90f, 90);
283+
_previewDir.y += yaw;
284+
_previewQuaternion = Quaternion.Euler(_previewDir.x, 0, 0) * Quaternion.Euler(0, _previewDir.y, 0);
285+
break;
286+
case PreviewMeshRotationMethod.Freeform:
287+
_previewQuaternion = Quaternion.Euler(pitch, yaw, 0) * _previewQuaternion;
288+
break;
289+
}
290+
_previewQuaternion = Quaternion.Normalize(_previewQuaternion);
291+
}
292+
293+
#endregion
294+
}
295+
296+
public override TriElement CreateElement(TriProperty property, TriElement next)
297+
{
298+
var root = new TriBoxGroupElement(new TriBoxGroupElement.Props
299+
{
300+
titleMode = TriBoxGroupElement.TitleMode.Hidden,
301+
});
302+
root.AddChild(new PreviewMeshPicker(property, Attribute.UseFoldout));
303+
root.AddChild(new PreviewMesh(property, Attribute.Height, Attribute.Width, Attribute.UseFoldout, Attribute.RotationMethod));
304+
return root;
305+
}
306+
307+
}
308+
}

Editor.Extras/Drawers/PreviewMeshDrawer.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using TriInspector;
2+
using UnityEngine;
3+
4+
public class Decorators_PreviewMeshSample : ScriptableObject
5+
{
6+
[Title("Shortcut")]
7+
[InfoBox("Mouse click -> Rotate Mesh in preview")]
8+
[InfoBox("Shift + Scroll wheel -> Zoom Mesh in preview")]
9+
10+
[LabelWidth(200f)]
11+
[PreviewMesh]
12+
public GameObject mesh;
13+
14+
[LabelWidth(200f)]
15+
[PreviewMesh(300)]
16+
public GameObject meshHeight;
17+
18+
[LabelWidth(200f)]
19+
[PreviewMesh(200, 160)]
20+
public GameObject myObjectLengthAndWidth;
21+
22+
[LabelWidth(200f)]
23+
[PreviewMesh(200, 160, false)]
24+
public GameObject meshNoFoldout;
25+
26+
[LabelWidth(200f)]
27+
[PreviewMesh(200, 160, true, PreviewMeshRotationMethod.Freeform)]
28+
public GameObject meshFreeformRotation;
29+
}

Editor.Samples/Decorators/Decorators_PreviewMeshSample.cs.meta

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,24 @@ public float lengthInMeters;
288288
public float freeTextUnit;
289289
```
290290

291+
#### Preview Mesh
292+
293+
![Preview Mesh](https://github.com/user-attachments/assets/d589a769-dc55-4e96-b0cc-edcde2677760)
294+
295+
```csharp
296+
[LabelWidth(270f)]
297+
[PreviewMesh(100, 100)]
298+
public GameObject meshCustomLengthAndWidth;
299+
300+
[LabelWidth(200f)]
301+
[PreviewMesh(200, 160, false)]
302+
public GameObject meshNoFoldoutNoMesh;
303+
304+
[LabelWidth(200f)]
305+
[PreviewMesh(200, 160, false)]
306+
public GameObject meshNoFoldoutWithMesh;
307+
```
308+
291309
### Styling
292310

293311
#### Title

0 commit comments

Comments
 (0)