【问题标题】:How to update one Bezier curve as another is moved using a custom editor如何在使用自定义编辑器移动另一条贝塞尔曲线时更新一条贝塞尔曲线
【发布时间】:2018-11-01 08:00:20
【问题描述】:

我正在使用从here 获得的以下代码创建贝塞尔曲线。我还制作了一个BezierPair 游戏对象,它有两条贝塞尔曲线作为子对象。

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

  1. 我希望每条贝塞尔曲线的P0 在移动一条时始终保持不变。换句话说,我希望它们始终一起移动,并且可以选择关闭此移动。

  1. P1 两条曲线是分开的。如何使每条曲线的P1 沿相同方向移动相同距离?

  1. P2 两条曲线是分开的。如何沿着连接P0P3 的线制作一条曲线的P2 P2 的另一条曲线?请注意,镜像线将取自下例中的曲线 1,因为 curve1P2 已移动。如果curve2P2被移动,那么镜像线将从curve2P0P3中取出。

我不想在运行时这样做。所以必须使用自定义编辑器。 我尝试在下面的代码中解决 1. 但如果没有在层次结构窗口中选择 BezierPair,第二条曲线的位置不会更新

贝塞尔曲线:

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);
}
}

贝塞尔曲线:

[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;
}
}

贝塞尔曲线编辑器:

[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;
}
}

贝塞尔对:

public class BezierPair : MonoBehaviour {


public GameObject bez1;
public GameObject bez2;

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

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();
    }
}

【问题讨论】:

  • 3d 还是 2d 需要它?
  • @RodrigoRodrigues 我在 2d 中需要它
  • 你应该看看几何连续性。 Wikiimg1img2
  • @Bane,有什么适合你的答案吗?
  • @RodrigoRodrigues 我刚刚更新了问题

标签: c# unity3d


【解决方案1】:

已编辑:

我认为您不需要BezierPair 课程。我建议您添加对要“配对”的另一个 BezierCurve 对象的引用,作为 BezierCurve 类 (paired) 上的公共字段。另一条曲线将与这条曲线“配对”。一旦配对,可能会应用对运动的限制。您可以使用 3 个公共布尔字段 behavior1behavior2behavior3 来控制所需的行为。

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

贝塞尔曲线:

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:所需的行为是在处理程序绘图方法中编码的,因此您可以访问撤消和其他功能。

注意#3EditorUtility.SetDirtyconsidered obsolete,因为 Unity 5.3 用于将对象标记为脏以进行绘图,不应再用于修改场景中的对象Undo.RecordObject 完成这项工作。

贝塞尔曲线编辑器:

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 引用到另一个配对字段,并设置打开/关闭您想要的行为。

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

【讨论】:

  • 您正在另一条曲线中创建两条曲线。此外,积分并非全部更新。我刚刚更新了问题。
  • “创建 2 条曲线”是什么意思?它们不是被创建的,而是被引用的。您可以根据需要创建任意数量的曲线,并且可以使用此方法链接任意数量的曲线。但是,如果您希望它只使用 2 条曲线并且始终只使用 2 条曲线,请告诉我,以便我更新答案。
  • @Bane,我再次编辑,更正了局部 x 全局坐标的一些错误。现在它具有所需的行为。
  • 我喜欢贝塞尔曲线选择选项,我认为这真的很管用。我正在考虑解决方法[ExecuteInEditMode] 的主要原因是我预见自己会使用大量曲线。也许,一种仅在选择曲线更新时检查曲线更新的方法或可以做的事情。
  • 我认为这不需要另一个问题。在我看来,最好在这里解决它,而不是问另一个严重依赖于这个问题的问题。
【解决方案2】:

我尝试在下面的代码中解决 1. 但第二个的位置 如果没有我在层次结构中选择 BezierPair,曲线就不会更新 窗口

这是因为您在 BezierPairEditor 类中使用了 OnInspectorGUI() 回调。仅当BezierPairEditor 的检查器显示在您的编辑器窗口中时才会执行。因此,如果您不单击带有BezierPair 组件的GameObject,则不会显示BezierPair 的检查器,并且不会触发回调。

改为使用EditorApplication.update 委托。在您的 BezierPairEditor 类中:

void OnEnable() {
    EditorApplication.update += OnUpdate; //Register to the update callback
}

void OnUpdate() {
    //Implement your code to update positions here
}

其次,尝试缓存您的 BezierCurve 引用。这是因为您真的不想在每个更新循环中运行GetComponent,因为它的成本很高。同样,在您的 BezierPairEditor 类中(为了清楚起见,我将省略上面的代码,自行合并):

BezierCurve bez1C;
BezierCurve bez2C;

void OnEnable() {
    BezierPair pair = target as BezierPair;
    bez1C = pair.bez1.GetComponent<BezierCurve>();
    bez2C = pair.bez2.GetComponenet<BezierCurve>();
}

void OnUpdate() {
    //Do something with bez1C and bez2C here
}

最后,要解决第 1 点到第 3 点,由于您没有指定,我将假设您正在手动移动 bez1,并且应该跟随 bez2。如果其中一个可以手动移动而另一个应该跟随,我建议您在您的BezierCurve 类中实现isChanged 检查。然后在您的BezierPairEditor 类中,您可以检查哪一个已移动,并相应地更新另一个。

BezierPairEditor类内部(再次,为清楚起见,我将省略上面的代码,自行合并):

Vector2 bez1CPrev1;

void OnEnable() {
    bez1CPrev1 = bez1C.points[1];

    //Ensure that they have the same starting point
    //Either you shift just points[0] of bez2 to be the same as bez1, or you shift every point. Implement this yourself.
}

void OnUpdate() {
    Vector2 disp = bez1C.points[1] - bez1CPrev1;

    //1. 
    bez2C.points[0] = bez1C.points[0];

    //2.
    bez2C.points[1] = bez2C.points[1] + disp;

    //3. Here, we are gonna use Vector2.Reflect(). Why? 
    //Imagine throwing a ball from newP2(curve1) to P3(curve1).
    //If the 'surface' that the ball hits has a normal of P0(curve1)-P3(curve1), you will have the ball bounce back to reach newP2(curve2).
    //Effectively: newP2(curve2) = P3(curve1) + [newP2(curve2) - P3(curve1)]. The term in the square brackets, we will do it with Vector2.Reflect().
    bez2C.points[2] = bez1C.points[3] + Vector2.Reflect(bez1C.points[3] - bez1C.points[2], (bez1C.points[0] - bez1C.points[3]).normalized);

    //Draw your bezier curve etc. here

    bez1CPrev1 = bez1C.points[1];
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-21
    • 2020-06-23
    • 1970-01-01
    • 2011-12-06
    • 2011-03-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多