Immediate mode panels not positioning correctly & Immediate mode canvases drawing onto not-canvas cameras
Immediate mode drawing into Canvas works quite fine but there are some quirks I needed to adjust so that everything could work as expected. I hope that the code I changed could help improve the scripts for better usage for everyone. There are two issues I adressed:
1. Immediate mode panels do not work as expected when making them children of other recttransforms. The problem is on line 39 of ImmediateModePanel.cs:
Draw.Matrix *= Matrix4x4.TRS( tf.localPosition, tf.localRotation, tf.localScale );
The matrix doesn't take parent transforms into account. I fixed it by changing line 39 to
Draw.Matrix = Matrix4x4.TRS( tf.position, tf.rotation, tf.lossyScale);
2. This is perhaps not so much of a big issue but I have multiple cameras in my scene and I expected Shapes to render only to the camera that the immediate mode canvas corresponds to. Right now, this is not the case and shapes will draw everywhere. The weird thing is that there is already a reference for the camera in ImmediateModeCanvas.cs, but it is not used. I changed it to only render for the target camera + the scene view camera:
// Shapes © Freya Holmér - https://twitter.com/FreyaHolmer/
// Website & Documentation - https://acegikmo.com/shapes/
namespace Shapes
{
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
[ExecuteAlways]
[RequireComponent(typeof(Canvas))]
public class ImmediateModeCanvas : ImmediateModeShapeDrawer
{
Canvas canvas;
Canvas Canvas => canvas = canvas != null ? canvas : GetComponent();
RectTransform canvasRectTf;
RectTransform CanvasRectTf => canvasRectTf = canvasRectTf != null ? canvasRectTf : GetComponent();
Camera camUI;
Camera CamUI => camUI = camUI != null ? camUI : Canvas.worldCamera;
List panels = new List();
public void Add(ImmediateModePanel panel) => panels.Add(panel);
public void Remove(ImmediateModePanel panel) => panels.Remove(panel);
const double DEG_TO_RAD = 0.0174532925199432957692369076848861271344287188854172545609719144;
protected void DrawPanels()
{
foreach (ImmediateModePanel panel in panels)
{
panel.DrawPanel();
}
}
public override void DrawShapes(Camera cam)
{
if (Canvas.enabled == false)
return;
#if UNITY_EDITOR
if(!IsSceneViewCamera(cam) && cam != CamUI)
return;
#else
if (cam != CamUI)
return;
#endif
// this works for the scene view canvas
using (Draw.Command(cam))
{
Draw.ZTest = CompareFunction.Always;
Draw.Matrix = GetCanvasToWorldMatrix(cam);
DrawCanvasShapes(CanvasRectTf.rect);
}
}
public bool IsCameraBasedUI => Canvas.worldCamera != null && (Canvas.renderMode == RenderMode.ScreenSpaceCamera || Canvas.renderMode == RenderMode.WorldSpace);
bool CanUseSimpleCameraMatrix(Camera cam) => cam.cameraType == CameraType.SceneView || (IsCameraBasedUI && cam == Canvas.worldCamera);
Matrix4x4 GetCanvasToWorldMatrix(Camera cam)
{
if (CanUseSimpleCameraMatrix(cam))
return Canvas.transform.localToWorldMatrix;
// overlay cameras are a little more complicated,
// we have to construct a canvasToWorld matrix
float planeDistance = (cam.nearClipPlane + cam.farClipPlane) / 2;
RectTransform rtf = (RectTransform)Canvas.transform;
Transform camTf = cam.transform;
Vector3 forward = camTf.forward;
Vector3 origin = camTf.TransformPoint(0, 0, planeDistance);
float scale = 1;
// if perspective, then
if (cam.orthographic)
{
scale = 2 * cam.orthographicSize / rtf.sizeDelta.y;
}
else
{
double vFovHalfRad = (cam.fieldOfView * DEG_TO_RAD) / 2.0;
double halfYSize = (float)(planeDistance * Math.Tan(vFovHalfRad));
scale = (float)((2 * halfYSize) / rtf.sizeDelta.y);
}
Vector3 rightScale = camTf.right * scale;
Vector3 upScale = camTf.up * scale;
Vector3 frwScale = forward * scale; // todo
return new Matrix4x4(rightScale, upScale, frwScale, new Vector4(origin.x, origin.y, origin.z, 1));
}
/// The method to override in order to draw immediate mode shapes.
/// Note: This is called from an existing Draw.Command context
/// The full region the canvas is drawn to, in UI coordinates. Usually this is the full screen rect
public virtual void DrawCanvasShapes(Rect rect)
{
DrawPanels();
}
#if UNITY_EDITOR
bool IsSceneViewCamera(Camera camera)
{
if (camera == null)
return false;
foreach (UnityEditor.SceneView sceneView in UnityEditor.SceneView.sceneViews)
{
if (sceneView.camera == camera)
return true;
}
return false;
}
#endif
}
}
I would love to see those issues fixed in a future release of shapes :)
alright, I ended up spending way too long on this, but I've overhauled it for 4.4.0 and it should be much more stable and less experimental!