Skip to content

Instantly share code, notes, and snippets.

@JohannesMP
Last active March 18, 2021 13:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JohannesMP/b1f96770b55b1f9668e87ab536aae525 to your computer and use it in GitHub Desktop.
Save JohannesMP/b1f96770b55b1f9668e87ab536aae525 to your computer and use it in GitHub Desktop.
Proof of concept for fixing line thickness in the Unity Shapes Library when a point crosses through the camera's view plane.
using System;
using System.Collections.Generic;
using UnityEngine;
using Shapes;
#if UNITY_EDITOR
using UnityEditor;
#endif
[ExecuteAlways]
public class CloseThicknessExample : ImmediateModeShapeDrawer
{
[Min(0)]
public float pixelThickness = 50;
public bool applyThicknessFix = false;
public bool debugThicknessFixColor = true;
private const float DEBUG_PARAM_OFFSET = 0.00001f;
public Camera targetCam;
[Space]
public Transform pointA;
public Transform pointB;
public Color colorA = Color.red;
public Color colorB = Color.yellow;
[Space]
public bool usePolyline = false;
public bool volumetricLines = true;
[Space]
public Vector2 debugGUIOffset = new Vector2(10, 10);
private Color refColor = new Color(1, 1, 1, 0.25f);
private Color[] Colors => new [] { colorA, colorB };
private Vector3[] Points => new[] { pointA.position, pointB.position };
private PolylinePath path;
private void OnDestroy()
{
path?.Dispose();
}
public void Update()
{
path ??= new PolylinePath();
path.ClearAllPoints();
path.AddPoint(Points[0], pixelThickness, Colors[0]);
path.AddPoint(Points[1], pixelThickness, Colors[1]);
if (applyThicknessFix && targetCam != null)
{
PolylineScreenThicknessFix(path, targetCam);
}
}
public override void DrawShapes(Camera curCam)
{
if (pointA == null || pointB == null) return;
path ??= new PolylinePath();
using (Draw.Command(curCam))
{
Draw.ResetAllDrawStates();
Draw.BlendMode = ShapesBlendMode.Opaque;
Draw.LineThicknessSpace = ThicknessSpace.Pixels;
Draw.LineGeometry = volumetricLines ? LineGeometry.Volumetric3D : LineGeometry.Billboard;
if (usePolyline)
{
Draw.Polyline(path, 1);
}
else
{
for (int i = 0; i < path.Count-1; ++i)
{
DrawLine(path[i], path[i + 1]);
}
}
// Reference Spheres (show expected thickness)
Draw.BlendMode = ShapesBlendMode.Transparent;
Draw.SphereRadiusSpace = ThicknessSpace.Pixels;
for (int i = 0; i < Points.Length; ++i)
{
Draw.Sphere(Points[i], pixelThickness / 2, refColor);
}
}
}
private static void DrawLine(PolylinePoint from, PolylinePoint to)
{
Draw.Line(from.point, to.point, from.thickness, from.color, to.color);
}
// Overload without LocalToWorld Matrix
private void PolylineScreenThicknessFix(PolylinePath path, Camera cam) =>
PolylineScreenThicknessFix(path, cam, Matrix4x4.identity);
private List<PolylinePoint> _pointScratch = new List<PolylinePoint>();
private void PolylineScreenThicknessFix(
PolylinePath path, Camera cam, Matrix4x4 localToWorld)
{
if (cam == null || path.Count < 2) return;
Matrix4x4 localToCamNearPlane = Matrix4x4.Translate(new Vector3(0, 0, -cam.nearClipPlane)) *
cam.transform.worldToLocalMatrix * localToWorld;
PolylinePoint prev = path[0];
float prevDist = localToCamNearPlane.MultiplyPoint3x4(prev.point).z;
_pointScratch.Clear();
_pointScratch.Add(prev);
for (int pathIndex = 1; pathIndex < path.Count; ++pathIndex)
{
PolylinePoint cur = path[pathIndex];
float curDist = localToCamNearPlane.MultiplyPoint3x4(cur.point).z;
if (prevDist > 0 != curDist > 0)
{
float midParam = Mathf.InverseLerp(prevDist, curDist, 0);
Vector3 midPoint = Vector3.LerpUnclamped(prev.point, cur.point, midParam);
float midThickness = Mathf.LerpUnclamped(prev.thickness, cur.thickness, midParam);
if (debugThicknessFixColor)
{
if (prevDist > curDist)
{
_pointScratch.Add(new PolylinePoint(midPoint, Color.red, midThickness));
midParam -= DEBUG_PARAM_OFFSET;
if (midParam > 0)
{
midPoint = Vector3.LerpUnclamped(prev.point, cur.point, midParam);
_pointScratch.Add(new PolylinePoint(midPoint, Color.black, midThickness));
}
}
else
{
_pointScratch.Add(new PolylinePoint(midPoint, Color.black, midThickness));
midParam += DEBUG_PARAM_OFFSET;
if (midParam < 1)
{
midPoint = Vector3.Lerp(prev.point, cur.point, midParam);
_pointScratch.Add(new PolylinePoint(midPoint, Color.red, midThickness));
}
}
}
else
{
Color midColor = Color.LerpUnclamped(prev.color, cur.color, midParam);
_pointScratch.Add(new PolylinePoint(midPoint, midColor, midThickness));
}
}
_pointScratch.Add(cur);
prev = cur;
prevDist = curDist;
}
if (_pointScratch.Count == path.Count) return;
path.ClearAllPoints();
path.AddPoints(_pointScratch);
}
#if UNITY_EDITOR
// Stuff to help debug draw view frustums and on-screen debug text
public override void OnEnable()
{
base.OnEnable();
EditorApplication.update += RedrawAllSceneViews;
}
public override void OnDisable()
{
base.OnDisable();
EditorApplication.update -= RedrawAllSceneViews;
}
private static void RedrawAllSceneViews()
{
foreach (SceneView view in SceneView.sceneViews)
{
view.Repaint();
}
}
private void OnDrawGizmos()
{
if (targetCam == null) return;
Camera viewCam = Camera.current;
Color frustumColor = Color.cyan;
frustumColor.a = 0.5f;
Color color = frustumColor;
color.a = 0.4f;
Gizmos.color = color;
Gizmos.matrix = targetCam.transform.localToWorldMatrix;
Gizmos.DrawFrustum(Vector3.zero, targetCam.fieldOfView,
10, targetCam.nearClipPlane, targetCam.aspect);
if (pointA == null || pointB == null) return;
Vector3 viewPoint0 = targetCam.WorldToViewportPoint(Points[0]);
DrawScreenText(viewCam, debugGUIOffset,
$"Point0: {viewPoint0.x:0.00}, {viewPoint0.y:0.00}, {viewPoint0.z:0.00}", Colors[0]);
Vector3 viewPoint1 = targetCam.WorldToViewportPoint(Points[1]);
DrawScreenText(viewCam, debugGUIOffset + new Vector2(0, 15),
$"Point1: {viewPoint1.x:0.00}, {viewPoint1.y:0.00}, {viewPoint1.z:0.00}", Colors[1]);
}
private static void DrawScreenText(Camera cam, Vector2 screenPos, string text, Color color)
{
GUIStyle style = new GUIStyle { normal = { textColor = color } };
Vector3 topLeft = new Vector3(screenPos.x, cam.pixelHeight - screenPos.y, 1);
Handles.Label(cam.ScreenToWorldPoint(topLeft), text, style);
}
#endif
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment