-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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