Created
December 10, 2019 17:43
-
-
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
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 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; | |
} | |
} | |
} | |
} |
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 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