Как обновить одну кривую Безье при перемещении другой с помощью специального редактора - PullRequest
0 голосов
/ 22 мая 2018

Я создаю кривые Безье, используя приведенный ниже код, полученный из здесь .Я также сделал игровой объект BezierPair, у которого в качестве дочерних объектов есть две кривые Безье.

Из соответствующих изображений ниже и BezierPair, где points[0] ... points[3] представлено как P0 ... P3:

  1. Я хочу P0 каждой кривой Безье всегда остаются неизменными при перемещении.Другими словами, я хочу, чтобы они всегда двигались вместе, с возможностью отключения этого движения.

enter image description here

Скажите, P1 обеих кривых расходятся.Как я могу заставить P1 каждой кривой двигаться в одном направлении на одинаковом расстоянии?

enter image description here

Скажите, P2 обеих кривых расходятся.Как я могу сделать P2 из одного кривого зеркала P2 из другой кривой вдоль линии, соединяющей P0 и P3?Обратите внимание, что зеркальная линия будет взята из кривой 1 в примере ниже, потому что curve1 's P2 перемещено.Если curve2 s P2 перемещено, то линия зеркала будет взята из curve2 s P0P3.

enter image description here

Я не хочу делать это во время выполнения.Таким образом, пользовательский редактор должен быть использован.Я попытался решить 1. в приведенном ниже коде, но позиция для второй кривой не изменилась бы без моего выбора BezierPair в окне иерархии

Bezier:

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:

[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:

[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:

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

Ответы [ 2 ]

0 голосов
/ 01 июня 2018

Я попытался решить 1. в приведенном ниже коде, но позиция для второй кривой не изменилась бы без моего выбора BezierPair в окне иерархии

Это потому, что вы используете OnInspectorGUI() обратный вызов внутри вашего BezierPairEditor класса.Это выполняется только тогда, когда инспектор BezierPairEditor отображается в окне вашего редактора.Таким образом, если вы не нажмете GameObject с компонентом BezierPair, инспектор 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 должно следовать.В случае, когда один из них может быть перемещен вручную, а другой должен следовать, я бы порекомендовал вам реализовать проверку isChanged в вашем классе BezierCurve.Затем в своем классе 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];
}
0 голосов
/ 01 июня 2018

РЕДАКТИРОВАНИЕ:

Не думаю, что вам понадобится ваш класс BezierPair.Я предлагаю вам добавить ссылку на другой BezierCurve объект, который вы хотите "спарить", как открытое поле в классе BezierCurve (paired).Эта другая кривая будет «спарена» с этой кривой.После сопряжения могут применяться ограничения на движение.Вы можете управлять желаемым поведением с помощью 3 открытых bool-полей behavior1, behavior2 и behavior3.

Примечание # 1 : вместо вызова метода DrawBezierCurve изРедактор, я добавил [ExecuteInEditMode] в класс компонентов.Таким образом, вы не смешиваете обязанности между Компонентом и Редактором: компонент BezierCurve рисует сам на сцене, в то время как BezierCurveEditor управляет только логикой редактирования, такой как применение ограничений и рисование обработчиков.

BezierCurve:

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 : желаемое поведение было закодировано в методах рисования обработчика, поэтому у вас есть доступ к отмене и другим функциям.

Примечание# 3 : EditorUtility.SetDirty считается устаревшим , поскольку Unity 5.3 для пометки объектов как грязных для рисования, и больше не должны использоваться для изменения объектов в сценах .Undo.RecordObject выполняет работу.

BezierCurveEditor:

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, чтобы получить крутые градиенты или изменение ширины (например, мазок кисти).Если у вас есть узел острия, и вы хотите, чтобы он выглядел непрерывно, увеличьте значение End Cap Vertices в Line Renderer.Используйте Sprites-Default в качестве материала для 2D.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...