Skip to content

Instantly share code, notes, and snippets.

@keenanwoodall
Created December 10, 2019 17:43
Show Gist options
  • Save keenanwoodall/105a66e75f4270e83437a804eb823145 to your computer and use it in GitHub Desktop.
Save keenanwoodall/105a66e75f4270e83437a804eb823145 to your computer and use it in GitHub Desktop.
A modified bend deformer that only modifies vertices within a defined bounding box
using UnityEngine;
using Unity.Jobs;
using Unity.Burst;
using Unity.Collections;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using Beans.Unity.Mathematics;
namespace Deform
{
[Deformer (Name = "Bounded Bend (WIP)", Description = "Bends a mesh within a bounding box", Type = typeof (BoundedBendDeformer))]
public class BoundedBendDeformer : Deformer, IFactor
{
public float Angle
{
get => angle;
set => angle = value;
}
public float Factor
{
get => factor;
set => factor = value;
}
public BoundsMode Mode
{
get => mode;
set => mode = value;
}
public Bounds Bounds
{
get => bounds;
set => bounds = value;
}
public Transform Axis
{
get
{
if (axis == null)
axis = transform;
return axis;
}
set { axis = value; }
}
[SerializeField, HideInInspector] private float angle;
[SerializeField, HideInInspector] private float factor = 1f;
[SerializeField, HideInInspector] private BoundsMode mode = BoundsMode.Limited;
[SerializeField, HideInInspector] Bounds bounds;
[SerializeField, HideInInspector] private Transform axis;
public override DataFlags DataFlags => DataFlags.Vertices;
public override JobHandle Process (MeshData data, JobHandle dependency = default (JobHandle))
{
var totalAngle = Angle * Factor;
if (Mathf.Approximately (totalAngle, 0f) || Mathf.Approximately (Bounds.size.y, 0f))
return dependency;
var meshToAxis = DeformerUtils.GetMeshToAxisSpace (Axis, data.Target.GetTransform ());
switch (mode)
{
default:
case BoundsMode.Unlimited:
return new UnlimitedBendJob
{
angle = totalAngle,
bounds = Bounds,
meshToAxis = meshToAxis,
axisToMesh = meshToAxis.inverse,
vertices = data.DynamicNative.VertexBuffer
}.Schedule (data.Length, DEFAULT_BATCH_COUNT, dependency);
case BoundsMode.Limited:
return new LimitedBendJob
{
angle = totalAngle,
bounds = Bounds,
meshToAxis = meshToAxis,
axisToMesh = meshToAxis.inverse,
vertices = data.DynamicNative.VertexBuffer
}.Schedule (data.Length, DEFAULT_BATCH_COUNT, dependency);
}
}
[BurstCompile (CompileSynchronously = COMPILE_SYNCHRONOUSLY)]
public struct UnlimitedBendJob : IJobParallelFor
{
public float angle;
public bounds bounds;
public float4x4 meshToAxis;
public float4x4 axisToMesh;
public NativeArray<float3> vertices;
public void Execute (int index)
{
var point = mul (meshToAxis, float4 (vertices[index], 1f));
var angleRadians = radians (angle) * (1f / (bounds.max.y - bounds.min.y));
var scale = 1f / angleRadians;
var rotation = point.y * angleRadians;
var c = cos ((float)PI - rotation);
var s = sin ((float)PI - rotation);
point.xy = float2
(
(scale * c) + scale - (point.x * c),
(scale * s) - (point.x * s)
);
vertices[index] = mul (axisToMesh, point).xyz;
}
}
[BurstCompile (CompileSynchronously = COMPILE_SYNCHRONOUSLY)]
public struct LimitedBendJob : IJobParallelFor
{
public float angle;
public bounds bounds;
public float4x4 meshToAxis;
public float4x4 axisToMesh;
public NativeArray<float3> vertices;
public void Execute (int index)
{
var point = mul (meshToAxis, float4 (vertices[index], 1f));
if (point.x > bounds.max.x || point.x < bounds.min.x || point.z > bounds.max.z || point.z < bounds.min.z || point.y < bounds.min.y)
return;
var unbentPoint = point;
var top = bounds.max.y;
var bottom = bounds.min.y;
var angleRadians = radians (angle);
var scale = 1f / (angleRadians * (1f / (top - bottom)));
var rotation = (clamp (point.y, bottom, top) - bottom) / (top - bottom) * angleRadians;
var c = cos ((float)PI - rotation);
var s = sin ((float)PI - rotation);
point.xy = float2
(
(scale * c) + scale - (point.x * c),
(scale * s) - (point.x * s)
);
if (unbentPoint.y > top)
{
point.y += -c * (unbentPoint.y - top);
point.x += s * (unbentPoint.y - top);
}
else if (unbentPoint.y < bottom)
{
point.y += -c * (unbentPoint.y - bottom);
point.x += s * (unbentPoint.y - bottom);
}
point.y += bottom;
vertices[index] = mul (axisToMesh, point).xyz;
}
}
}
}
using UnityEngine;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using Beans.Unity.Editor;
using Deform;
namespace DeformEditor
{
[CustomEditor (typeof (BoundedBendDeformer)), CanEditMultipleObjects]
public class BoundedBendDeformerEditor : DeformerEditor
{
private static class Content
{
public static readonly GUIContent Angle = new GUIContent (text: "Angle", tooltip: "How many degrees the mesh should be bent by the time it reaches the top bounds.");
public static readonly GUIContent Factor = DeformEditorGUIUtility.DefaultContent.Factor;
public static readonly GUIContent Mode = new GUIContent (text: "Mode", tooltip: "Unlimited: Entire mesh is bent.\nLimited: Mesh is only bent between bounds.");
public static readonly GUIContent Bounds = new GUIContent (text: "Bounds", tooltip: "Any vertices outside this will be fully unbent.");
public static readonly GUIContent Axis = DeformEditorGUIUtility.DefaultContent.Axis;
}
private class Properties
{
public SerializedProperty Angle;
public SerializedProperty Factor;
public SerializedProperty Mode;
public SerializedProperty Bounds;
public SerializedProperty Axis;
public Properties (SerializedObject obj)
{
Angle = obj.FindProperty ("angle");
Factor = obj.FindProperty ("factor");
Mode = obj.FindProperty ("mode");
Bounds = obj.FindProperty ("bounds");
Axis = obj.FindProperty ("axis");
}
}
private Properties properties;
private ArcHandle angleHandle = new ArcHandle ();
//private readonly VerticalBoundsHandle boundsHandle = new VerticalBoundsHandle ();
private BoxBoundsHandle boxHandle = new BoxBoundsHandle();
protected override void OnEnable ()
{
base.OnEnable ();
properties = new Properties (serializedObject);
}
public override void OnInspectorGUI ()
{
base.OnInspectorGUI ();
serializedObject.UpdateIfRequiredOrScript ();
EditorGUILayout.PropertyField (properties.Angle, Content.Angle);
EditorGUILayout.PropertyField (properties.Factor, Content.Factor);
EditorGUILayout.PropertyField (properties.Mode, Content.Mode);
using (new EditorGUI.IndentLevelScope ())
{
EditorGUILayout.PropertyField(properties.Bounds, Content.Bounds);
}
EditorGUILayout.PropertyField (properties.Axis, Content.Axis);
serializedObject.ApplyModifiedProperties ();
EditorGUILayoutx.WIPAlert();
EditorApplication.QueuePlayerLoopUpdate ();
}
public override void OnSceneGUI ()
{
base.OnSceneGUI ();
var bend = target as BoundedBendDeformer;
DrawAngleHandle (bend);
boxHandle.handleColor = DeformEditorSettings.SolidHandleColor;
boxHandle.wireframeColor = DeformEditorSettings.LightHandleColor;
boxHandle.center = bend.Bounds.center;
boxHandle.size = bend.Bounds.size;
using (new Handles.DrawingScope(Matrix4x4.TRS(bend.Axis.position, bend.Axis.rotation, bend.Axis.lossyScale)))
{
using (var check = new EditorGUI.ChangeCheckScope())
{
boxHandle.DrawHandle();
if (check.changed)
{
Undo.RecordObject(bend, "Changed Bounds");
bend.Bounds = new Bounds(boxHandle.center, boxHandle.size);
SceneView.RepaintAll();
}
}
}
EditorApplication.QueuePlayerLoopUpdate ();
}
private void DrawAngleHandle (BoundedBendDeformer bend)
{
var handleRotation = bend.Axis.rotation * Quaternion.Euler (-90, 0f, 0f);
// There's some weird issue where if you pass the normal lossyScale, the handle's scale on the y axis is changed when the transform's z axis is changed.
// My simple solution is to swap the y and z.
var handleScale = new Vector3
(
x: bend.Axis.lossyScale.x,
y: bend.Axis.lossyScale.z,
z: bend.Axis.lossyScale.y
);
var matrix = Matrix4x4.TRS (bend.Axis.position + bend.Axis.up * bend.Bounds.min.y * bend.Axis.lossyScale.y, handleRotation, handleScale);
var radiusDistanceOffset = HandleUtility.GetHandleSize (bend.Axis.position + bend.Axis.up * bend.Bounds.max.y) * DeformEditorSettings.ScreenspaceSliderHandleCapSize * 2f;
angleHandle.angle = bend.Angle;
angleHandle.radius = (bend.Bounds.max.y - bend.Bounds.min.y) + radiusDistanceOffset;
angleHandle.fillColor = Color.clear;
using (new Handles.DrawingScope (DeformEditorSettings.SolidHandleColor, matrix))
{
using (var check = new EditorGUI.ChangeCheckScope ())
{
angleHandle.DrawHandle ();
if (check.changed)
{
Undo.RecordObject (bend, "Changed Angle");
bend.Angle = angleHandle.angle;
}
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment