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+ }
0 commit comments