如何使用自定义编辑器在移动另一个Bezier曲线时更新它 [英] How to update one Bezier curve as another is moved using a custom editor

查看:66
本文介绍了如何使用自定义编辑器在移动另一个Bezier曲线时更新它的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用下面从此处获得的代码创建贝塞尔曲线>.我还制作了一个BezierPair游戏对象,该对象具有两个Bézier曲线作为子对象.

I am creating Bézier curves using the code below which I got from here. I have also made a BezierPair game object which has two Bézier curves as child objects.

从下面和BezierPair的相应图像中,其中points[0] ... points[3]表示为P0 ... P3:

From the respective images below and BezierPair, where points[0]...points[3] is represented as P0...P3:

  1. 我希望每个Bézier曲线的P0在移动时始终保持不变.换句话说,我希望他们始终在一起移动,并可以选择关闭此移动.
  1. I want P0 of each Bézier curve always remain the same when one is moved. In other words I want them to move together always, with the option of turning this movement off.

    两条曲线的
  1. P1分开.如何使每个曲线的P1沿相同的方向移动相同的距离?
  1. Say P1 of both curves are apart. How can I make P1 of each curve move in the same direction covering the same distance?

  1. 说两条曲线的P2是分开的.如何沿着连接P0P3的线制作一个曲线镜像的P2另一曲线的P2?请注意,由于移动了curve1P2,因此在下面的示例中将从曲线1截取镜像线.如果curve2P2被移动,则镜像线将从curve2P0P3中获取.
  1. Say P2 of both curves are apart. How can I make P2 of one curve mirror P2 of another curve along a line joining P0 and P3? Note that the mirror line would be taken from the curve 1 in the example below because curve1's P2 is moved. If curve2's P2 is moved, then the mirror line will be taken from curve2's P0P3.

我不想在运行时执行此操作.因此,必须使用自定义编辑器. 我尝试在下面的代码中求解1.,但是如果不在层次结构窗口中选择BezierPair,第二条曲线的位置就不会更新

I don’t want to do this at run time. So a custom editor has to be used. I tried solving 1. in the code below but the position for the second curve wouldn’t update without my selecting BezierPair in the hierarchy window

贝济耶:

public static class Bezier {

public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2,   Vector3 p3, float t) {
t = Mathf.Clamp01(t);
float oneMinusT = 1f - t;
return
    oneMinusT * oneMinusT * oneMinusT * p0 +
    3f * oneMinusT * oneMinusT * t * p1 +
    3f * oneMinusT * t * t * p2 +
    t * t * t * p3;
}

public static Vector3 GetFirstDerivative (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) {
t = Mathf.Clamp01(t);
float oneMinusT = 1f - t;
return
    3f * oneMinusT * oneMinusT * (p1 - p0) +
    6f * oneMinusT * t * (p2 - p1) +
    3f * t * t * (p3 - p2);
}
}

BezierCurve:

BezierCurve:

[RequireComponent(typeof(LineRenderer))]
public class BezierCurve : MonoBehaviour {

public Vector3[] points;
LineRenderer lr;
public int numPoints = 49;
bool controlPointsChanged = false;

bool isMoving = false;

public void Reset () {
points = new Vector3[] {
    new Vector3(1f, 0f, 0f),
    new Vector3(2f, 0f, 0f),
    new Vector3(3f, 0f, 0f),
    new Vector3(4f, 0f, 0f)
};
}

void Start()    {

lr = GetComponent<LineRenderer> ();
lr.positionCount = 0;
DrawBezierCurve ();

}
public Vector3 GetPoint (float t) {
return transform.TransformPoint(Bezier.GetPoint(points[0], points[1], points[2], points[3], t));
}

public void DrawBezierCurve ()  {
lr = GetComponent<LineRenderer> ();
lr.positionCount = 1;
lr.SetPosition(0, points[0]);

for (int i = 1; i < numPoints+1; i++) {
    float t = i / (float)numPoints;
    lr.positionCount = i+1;
    lr.SetPosition(i, GetPoint(t));
}
}

public Vector3 GetVelocity (float t) {
return transform.TransformPoint(
    Bezier.GetFirstDerivative(points[0], points[1], points[2], points[3], t)) - transform.position;
}

public Vector3 GetDirection (float t) {
return GetVelocity(t).normalized;
}
}

BezierCurveEditor:

BezierCurveEditor:

[CustomEditor(typeof(BezierCurve))]
public class BezierCurveEditor : Editor {

private BezierCurve curve;
private Transform handleTransform;
private Quaternion handleRotation;

private const int lineSteps = 10;

private const float directionScale = 0.5f;

private void OnSceneGUI () {
curve = target as BezierCurve;
handleTransform = curve.transform;
handleRotation = Tools.pivotRotation == PivotRotation.Local ?
    handleTransform.rotation : Quaternion.identity;

Vector3 p0 = ShowPoint(0);
Vector3 p1 = ShowPoint(1);
Vector3 p2 = ShowPoint(2);
Vector3 p3 = ShowPoint(3);

Handles.color = Color.gray;
Handles.DrawLine(p0, p1);
Handles.DrawLine(p2, p3);
Handles.DrawBezier(p0, p3, p1, p2, Color.white, null, 2f);

curve.DrawBezierCurve ();

if (GUI.changed) {
    curve.DrawBezierCurve ();
    EditorUtility.SetDirty( curve );
    Repaint();
}

}


private void ShowDirections () {
Handles.color = Color.green;
Vector3 point = curve.GetPoint(0f);
Handles.DrawLine(point, point + curve.GetDirection(0f) * directionScale);
for (int i = 1; i <= lineSteps; i++) {
    point = curve.GetPoint(i / (float)lineSteps);
    Handles.DrawLine(point, point + curve.GetDirection(i / (float)lineSteps) * directionScale);
}
}

private Vector3 ShowPoint (int index) {
Vector3 point = handleTransform.TransformPoint(curve.points[index]);
EditorGUI.BeginChangeCheck();
point = Handles.DoPositionHandle(point, handleRotation);
if (EditorGUI.EndChangeCheck()) {
    Undo.RecordObject(curve, "Move Point");
    EditorUtility.SetDirty(curve);
    curve.points[index] = handleTransform.InverseTransformPoint(point);
}
return point;
}
}

BezierPair:

BezierPair:

public class BezierPair : MonoBehaviour {


public GameObject bez1;
public GameObject bez2;

public void setupCurves()   {
    bez1 = GameObject.Find("Bez1");
    bez2 = GameObject.Find("Bez2");
}
}

BezierPairEditor:

BezierPairEditor:

[CustomEditor(typeof(BezierPair))]
public class BezierPairEditor : Editor {

private BezierPair bezPair;

 public override void OnInspectorGUI()
{
    bezPair = target as BezierPair;

    if (bezPair.bez1.GetComponent<BezierCurve>().points[0] != bezPair.bez2.GetComponent<BezierCurve>().points[0])
    {
        Vector3 assignPoint0 = bezPair.bez1.GetComponent<BezierCurve>().points[0];
        bezPair.bez2.GetComponent<BezierCurve>().points[0] = assignPoint0;

    }
     if (GUI.changed)
    {

        EditorUtility.SetDirty(bezPair.bez1);
        EditorUtility.SetDirty(bezPair.bez2);
        Repaint();
    }
}

推荐答案

我认为您不需要BezierPair类.我建议您添加对要配对"的另一个BezierCurve对象的引用,作为对BezierCurve类(paired)的公共字段.另一条曲线将与此曲线配对".一旦配对,就可以应用对运动的约束.您可以使用3个公共布尔字段behavior1behavior2behavior3来控制所需的行为.

I dont think you'll need your BezierPair class. I suggest you to add a reference to the other BezierCurve object you want to "pair" as a public field on the BezierCurve class (paired). That other curve will get "paired" with this curve. Once paired, the constraints on the movement may apply. You can control the desired behaviors with the 3 public bool fields behavior1, behavior2 and behavior3.

注释#1 :我没有从编辑器中调用方法DrawBezierCurve,而是将[ExecuteInEditMode]添加到了组件类中.这样,您就不会在组件和编辑器之间混合职责:BezierCurve组件会在场景中绘制自己,而BezierCurveEditor仅管理编辑逻辑,例如应用约束和绘制处理程序.

Note#1: Instead of calling the method DrawBezierCurve from the Editor, I added the [ExecuteInEditMode] to the Component Class. This way, You don't mix responsibilities between the Component and the Editor: The BezierCurve component draws itself on the scene, while the BezierCurveEditor only manages edit logic, like applying the constraints and drawing the handlers.

using UnityEngine;

[RequireComponent(typeof(LineRenderer))]
[ExecuteInEditMode] // Makes Update() being called often even in Edit Mode
public class BezierCurve : MonoBehaviour
{

  public Vector3[] points;
  public int numPoints = 50;
  // Curve that is paired with this curve
  public BezierCurve paired;
  public bool behavior1; // check on editor if you desired behavior 1 ON/OFF
  public bool behavior2; // check on editor if you desired behavior 2 ON/OFF
  public bool behavior3; // check on editor if you desired behavior 3 ON/OFF
  LineRenderer lr;

  void Reset()
  {
    points = new Vector3[]
    {
      new Vector3(1f, 0f, 0f),
      new Vector3(2f, 0f, 0f),
      new Vector3(3f, 0f, 0f),
      new Vector3(4f, 0f, 0f)
    };
  }

  void Start()
  {
    lr = GetComponent<LineRenderer>();
  }

  void Update()
  {
    // This component is the only responsible for drawing itself.
    DrawBezierCurve();
  }

  // This method is called whenever a field is changed on Editor
  void OnValidate()
  {
    // This avoids pairing with itself
    if (paired == this) paired = null;
  }

  void DrawBezierCurve()
  {
    lr.positionCount = numPoints;
    for (int i = 0; i < numPoints; i++)
    {
      // This corrects the "strange" extra point you had with your script.
      float t = i / (float)(numPoints - 1);
      lr.SetPosition(i, GetPoint(t));
    }
  }

  public Vector3 GetPoint(float t)
  {
    return transform.TransformPoint(Bezier.GetPoint(points[0], points[1], points[2], points[3], t));
  }

  public Vector3 GetVelocity(float t)
  {
    return transform.TransformPoint(Bezier.GetFirstDerivative(points[0], points[1], points[2], points[3], t)) - transform.position;
  }

  public Vector3 GetDirection(float t)
  {
    return GetVelocity(t).normalized;
  }
}

注意#2 :所需的行为已在处理程序绘制方法中进行了编码,因此您可以访问撤消"和其他功能.

Note#2: The desired behavior were coded inside the handler drawing methods, so you have access to Undo and other features.

注释#3 :EditorUtility.SetDirty认为已过时自Unity 5.3以来(用于将对象标记为肮脏的绘图对象),并且不再用于修改场景中的对象. Undo.RecordObject做这项工作.

Note#3: EditorUtility.SetDirty is considered obsolete since Unity 5.3 for marking objects as dirty for drawing, and should no-longer be used for modifying objects in scenes. Undo.RecordObject does the job.

using UnityEngine;
using UnityEditor;

// This attribute allows you to select multiple curves and manipulate them all as a whole on Scene or Inspector
[CustomEditor(typeof(BezierCurve)), CanEditMultipleObjects]
public class BezierCurveEditor : Editor
{
  BezierCurve curve;
  Transform handleTransform;
  Quaternion handleRotation;
  const int lineSteps = 10;
  const float directionScale = 0.5f;

  BezierCurve prevPartner; // Useful later.

  void OnSceneGUI()
  {
    curve = target as BezierCurve;
    if (curve == null) return;
    handleTransform = curve.transform;
    handleRotation = Tools.pivotRotation == PivotRotation.Local ? handleTransform.rotation : Quaternion.identity;

    Vector3 p0 = ShowPoint(0);
    Vector3 p1 = ShowPoint(1);
    Vector3 p2 = ShowPoint(2);
    Vector3 p3 = ShowPoint(3);

    Handles.color = Color.gray;
    Handles.DrawLine(p0, p1);
    Handles.DrawLine(p2, p3);
    Handles.DrawBezier(p0, p3, p1, p2, Color.white, null, 2f);

    // Handles multiple selection
    var sel = Selection.GetFiltered(typeof(BezierCurve), SelectionMode.Editable);
    if (sel.Length == 1)
    {
      // This snippet checks if you just attached or dettached another curve,
      // so it updates the attached member in the other curve too automatically
      if (prevPartner != curve.paired)
      {
        if (prevPartner != null) { prevPartner.paired = null; }
        prevPartner = curve.paired;
      }
    }
    if (curve.paired != null & curve.paired != curve)
    {
      // Pair the curves.
      var partner = curve.paired;
      partner.paired = curve;
      partner.behavior1 = curve.behavior1;
      partner.behavior2 = curve.behavior2;
      partner.behavior3 = curve.behavior3;
    }
  }

  // Constraints for a curve attached to back
  // The trick here is making the object being inspected the "master" and the attached object is adjusted to it.
  // This way, you avoid the conflict of one object trying to move the other.
  // [ExecuteInEditMode] on component class makes it posible to have real-time drawing while editing.
  // If you were calling DrawBezierCurve from here, you would only see updates on the other curve when you select it
  Vector3 ShowPoint(int index)
  {
    var thisPts = curve.points;
    Vector3 point = handleTransform.TransformPoint(thisPts[index]);
    EditorGUI.BeginChangeCheck();
    point = Handles.DoPositionHandle(point, handleRotation);
    if (EditorGUI.EndChangeCheck())
    {
      if (curve.paired != null && curve.paired != curve)
      {
        Undo.RecordObjects(new Object[] { curve, curve.paired }, "Move Point " + index.ToString());
        var pairPts = curve.paired.points;
        var pairTransform = curve.paired.transform;
        switch (index)
        {
          case 0:
            {
              if (curve.behavior1)
              {
                pairPts[0] = pairTransform.InverseTransformPoint(point);
              }
              break;
            }
          case 1:
            {
              if (curve.behavior2)
              {
                var p1 = handleTransform.TransformPoint(thisPts[1]);
                pairPts[1] += pairTransform.InverseTransformVector(point - p1);
              }
              break;
            }
          case 2:
            {
              if (curve.behavior3)
              {
                var p0 = handleTransform.TransformPoint(thisPts[0]);
                var p3 = handleTransform.TransformPoint(thisPts[3]);
                var reflect = Vector3.Reflect(p3 - point, (p3 - p0).normalized);
                pairPts[2] = pairTransform.InverseTransformPoint(p3 + reflect);
              }
              break;
            }
          default:
            break;
        }
      }
      else
      {
        Undo.RecordObject(curve, "Move Point " + index.ToString());
      }
      thisPts[index] = handleTransform.InverseTransformPoint(point);
    }
    return point;
  }
}

要使其正常运行,请通过检查器将一个BezierCurve引用到另一个的配对字段,然后将所需的行为设置为ON/OFF.

For have it working, reference one BezierCurve to another's paired field through inspector, and set ON/OFF the behaviors you want.

提示:修补LineRenderer的属性以获得凉爽的渐变或宽度变化(例如笔触).如果您有一个尖点节点并希望它看起来连续,请在Line Renderer上增加End Cap Vertices的值.将Sprites-Default用作2D材质.

Tips: tinker the properties of the LineRenderer to get cool gradients or width variation (like a brush stroke). If you have a cusp node and want it to look continuous, increase the value of End Cap Vertices on the Line Renderer. Use Sprites-Default as Material, for 2D.

这篇关于如何使用自定义编辑器在移动另一个Bezier曲线时更新它的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆