[ Curves and Splines ]
Create a custom editor;
Draw in the scene view;
Support editing via the scene view;
Create Beziér curves and understand the math behind them;
Draw curves and their direction of movement.
Build Beziér splines by combining curves;
Support free, aligned, and mirrored control points;
Support looping splines;
Move and place objects along a spline.
[ 1 Lines ]
Menu : GameObject >> Create Empty
Name : Line
Line Script 생성
Create >> C#
Name : Line
직선을 표현하기 위해 필요로 하는 최소 정보인 두정점을 담을 수 있는 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Line : MonoBehaviour {
public Vector3 p0, p1;
}
Line(Sctipt) 드래그 >> (to) Line(GameObject)
[ CustomEditor ]
유니티 에디터 상의 인스펙터나 씬을 커스터마이즈하기 위해 사용하는 Attribute
Editor와 관련된 코드들은 "Editor"라는 폴더를 만들어 그 안에 위치시켜야 함
또한 using UnityEditor를 사용해야 함
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(Line))] // Line 클래스에 대한 인스펙터 정의
public class LineInspector : Editor { // Editor를 상속받고 있음
private void OnSceneGUI () {
Line line = target as Line;
// 흰색 선으로 라인을 그림
Handles.color = Color.white;
Handles.DrawLine(line.p0, line.p1);
}
}
OnSceneGUI() : Unity에서 제공하는 특별한 이벤트 함수로 씬 내에 우리 컴퍼넌트에 대한 시각적인 내용들을 그릴 수 있도록 해줌
as : C#에서 제공하는 안전한 자료형 캐스팅을 위한 예약어
target : Editor 클래스가 OnSceneGUI가 호출될 때, 그려질 대상체로 target이라는 변수를 가지게 된다.
Handles : 오브젝트 대상체에 대한 시각화와 더불어 대상체를 조작 할 수 있도록 하는 Handle을 지원한다.
handles을 조작해도 Line은 움직이지 않음
Handle을 조작할 수 있도록 하기
기존의 지역 좌표계 라인을 전역 좌표계로 라인 그리기
private void OnSceneGUI () {
Line line = target as Line;
Transform handleTransform = line.transform;
Vector3 p0 = handleTransform.TransformPoint(line.p0);
Vector3 p1 = handleTransform.TransformPoint(line.p1);
Handles.color = Color.white;
Handles.DrawLine(p0, p1);
}
Transform.TransformPoint() : 지역 좌표계를 전역 좌표계로 만드는 함수
Line 정점에 Handle 붙이기
private void OnSceneGUI () {
Line line = target as Line;
Transform handleTransform = line.transform;
Quaternion handleRotation = handleTransform.rotation;
Vector3 p0 = handleTransform.TransformPoint(line.p0);
Vector3 p1 = handleTransform.TransformPoint(line.p1);
Handles.color = Color.white;
Handles.DrawLine(p0, p1);
Handles.DoPositionHandle(p0, handleRotation);
Handles.DoPositionHandle(p1, handleRotation);
}
DoPositionHandle() : Line을 생성하는 두 정점에 Handle을 붙일 수 있도록 하고 있음
Line의 정점들이 가지고 있는 rotation 정보를 가지고 설정할 수 있도록 함(글로벌 좌표계에서 가지고 있는 회전 정보를 넣으려면 Quaternion.identity를 넣으면 됨)
private void OnSceneGUI () {
Line line = target as Line;
Transform handleTransform = line.transform;
// 에디터에 설정된 좌표계(글러벌or로컬)를 얻어옴
Quaternion handleRotation = Tools.pivotRotation == PivotRotation.Local ?
handleTransform.rotation : Quaternion.identity;
Vector3 p0 = handleTransform.TransformPoint(line.p0);
Vector3 p1 = handleTransform.TransformPoint(line.p1);
Handles.color = Color.white;
Handles.DrawLine(p0, p1);
EditorGUI.BeginChangeCheck();
p0 = Handles.DoPositionHandle(p0, handleRotation);
if (EditorGUI.EndChangeCheck()) {
line.p0 = handleTransform.InverseTransformPoint(p0);
}
EditorGUI.BeginChangeCheck();
p1 = Handles.DoPositionHandle(p1, handleRotation);
if (EditorGUI.EndChangeCheck()) {
line.p1 = handleTransform.InverseTransformPoint(p1);
}
}
Tools.pivotRotation : 유니티 에디터의 좌표계
EditorGUI.BeginChangeCheck() ~ EditorGUI.EndChageCheck()
씬상에서 변화가 있는 부분을 체크한다.
InverseTransformPoint() : 월드(절대)좌표의 값으로 되어 있는 것을 상대 좌표값으로 치환하는 함수
유니티에서 저장 및 되돌리기 기능 추가
private void OnSceneGUI () {
Line line = target as Line;
Transform handleTransform = line.transform;
Quaternion handleRotation = Tools.pivotRotation == PivotRotation.Local ?
handleTransform.rotation : Quaternion.identity;
Vector3 p0 = handleTransform.TransformPoint(line.p0);
Vector3 p1 = handleTransform.TransformPoint(line.p1);
Handles.color = Color.white;
Handles.DrawLine(p0, p1);
EditorGUI.BeginChangeCheck();
p0 = Handles.DoPositionHandle(p0, handleRotation);
if (EditorGUI.EndChangeCheck()) {
Undo.RecordObject(line, "Move Point");
EditorUtility.SetDirty(line);
line.p0 = handleTransform.InverseTransformPoint(p0);
}
EditorGUI.BeginChangeCheck();
p1 = Handles.DoPositionHandle(p1, handleRotation);
if (EditorGUI.EndChangeCheck()) {
Undo.RecordObject(line, "Move Point");
EditorUtility.SetDirty(line);
line.p1 = handleTransform.InverseTransformPoint(p1);
}
Undo.RecordObject()
취소기능(Ctrl+Z)을 제공하기 위한 함수
EditorUtility.SetDirty()
유니티에셋을 실행중에 값을 변경하면 값이 저장이 되지 않고 날아가는데 그 값을 디스크에 저장해서 Asset값을 바꾸는 것
[ 2 Curves ]
BezierCurve
Menu : GameObject >> Create Empty
Name : Curve
Line Script 생성
Create >> C#
Name : BezierCurve
Bezier 곡선을 표현하기 위해 필요로 하는 최소 정보인 세정점을 담을 수 있는 스크립트
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BezierCurve : MonoBehaviour {
public Vector3[] points;
public void Reset () {
points = new Vector3[] {
new Vector3(1f, 0f, 0f),
new Vector3(2f, 0f, 0f),
new Vector3(3f, 0f, 0f)
};
}
}
Reset() :
Bezier Inspector 생성
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(BezierCurve))]
public class BezierCurveInspector : Editor {
private BezierCurve curve;
private Transform handleTransform;
private Quaternion handleRotation;
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);
Handles.color = Color.white;
Handles.DrawLine(p0, p1);
Handles.DrawLine(p1, p2);
}
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;
}
}
Vector3.Lerp() 함수를 이용한 베지어 곡선 만들기
BazierCurveInspector.cs
private const int lineSteps = 10;
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);
Handles.color = Color.gray;
Handles.DrawLine(p0, p1);
Handles.DrawLine(p1, p2);
Handles.color = Color.white;
Vector3 lineStart = curve.GetPoint(0f);
for (int i = 1; i <= lineSteps; i++) {
Vector3 lineEnd = curve.GetPoint(i / (float)lineSteps);
Handles.DrawLine(lineStart, lineEnd);
lineStart = lineEnd;
}
}
BezierCurve.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BezierCurve : MonoBehaviour {
public Vector3[] points;
public void Reset () {
points = new Vector3[] {
new Vector3(1f, 0f, 0f),
new Vector3(2f, 0f, 0f),
new Vector3(3f, 0f, 0f)
};
}
public Vector3 GetPoint (float t) {
return transform.TransformPoint(Bezier.GetPoint(points[0], points[1], points[2], t));
}
}
Bezier.cs
using UnityEngine;
public static class Bezier {
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
//return Vector3.Lerp(p0, p2, t);
return Vector3.Lerp(Vector3.Lerp(p0, p1, t), Vector3.Lerp(p1, p2, t), t);
}
}
d
[ Bezier 공식 만들기 ]
linear curve : B(t) = (1 - t) P0 + t P1
Bezier : B(t) = (1 - t) ((1 - t) P0 + t P1) + t ((1 - t) P1 + t P2)
공식을 축약하면, B(t) = (1 - t)2 P0 + 2 (1 - t) t P1 + t2 P2
공식을 정리해서 사용하면, 더이상 Vector3.Lerp를 세번 호출할 필요없이 사용할 수 있게됨
Bezier.cs 수정
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
// Clamp01 : 0 ~ 1 사이의 값으로 Clamp
t = Mathf.Clamp01 (t);
float oneMinusT = 1f - t;
return
oneMinusT * oneMinusT * p0 + 2f * oneMinusT * t * p1 + t * t * p2;
}
Bezier 곡선을 그리는 정점에 접선 그리기
미분 방정식 : 2 (1 - t) (P1 - P0) + 2 t (P2 - P1)
곡선에 접하는 직선의 방정식은 미분으로 구할 수 있음
Bezier.cs
public static Vector3 GetFirstDerivative (Vector3 p0, Vector3 p1, Vector3 p2, float t) {
return
2f * (1f - t) * (p1 - p0) +
2f * t * (p2 - p1);
}
t에 해당되는 부분의 직선의 기울기를 얻을 수 있음
BezierCurve.cs
public Vector3 GetVelocity (float t) {
return transform.TransformPoint(Bezier.GetFirstDerivative(points[0], points[1], points[2], t)) -
transform.position;
}
GetFirstDerivative로 얻어진 정점(방향값)은 월드 좌표계에 있는 Bezier 게임오브젝트의 transform.position을 기준으로 얻어진 정점(방향)이므로, 이 값을 빼줌(즉, 벡터 값만 받아올 수 있도록 해줌)
그러면 그냥 return Bezier.GetFirstDerivative( ...)만 해주면 되지 않나? >> 되는 듯...
BezierCurveInspector.cs
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);
Handles.color = Color.gray;
Handles.DrawLine(p0, p1);
Handles.DrawLine(p1, p2);
Handles.color = Color.white;
Vector3 lineStart = curve.GetPoint(0f);
Handles.color = Color.green;
Handles.DrawLine(lineStart, lineStart + curve.GetVelocity(0f));
for (int i = 1; i <= lineSteps; i++) {
Vector3 lineEnd = curve.GetPoint(i / (float)lineSteps);
Handles.color = Color.white;
Handles.DrawLine(lineStart, lineEnd);
Handles.color = Color.green;
Handles.DrawLine(lineEnd, lineEnd + curve.GetVelocity(i / (float)lineSteps));
lineStart = lineEnd;
}
}
Handles.DrawLine(lineEnd, LineEnd + curve.GetVeolcity(i / (float)lineSteps))
lineEnd 정점에서 GetVelocity함수를 통해 lineEnd에서 시작하는 직선의 기울기 값을 얻어내서 선을 그림
정점에서의 기울기 직선을 단위 벡터로 표현하기
BezierCurve.cs
public Vector3 GetDirection (float t) {
return GetVelocity(t).normalized;
}
BezierCurveInspector.cs
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);
Handles.color = Color.gray;
Handles.DrawLine(p0, p1);
Handles.DrawLine(p1, p2);
Handles.color = Color.white;
Vector3 lineStart = curve.GetPoint(0f);
Handles.color = Color.green;
//Handles.DrawLine(lineStart, lineStart + curve.GetVelocity(0f));
Handles.DrawLine(lineStart, lineStart + curve.GetDirection(0f));
for (int i = 1; i <= lineSteps; i++) {
Vector3 lineEnd = curve.GetPoint(i / (float)lineSteps);
Handles.color = Color.white;
Handles.DrawLine(lineStart, lineEnd);
Handles.color = Color.green;
//Handles.DrawLine(lineEnd, lineEnd + curve.GetVelocity(i / (float)lineSteps));
Handles.DrawLine(lineEnd, lineEnd + curve.GetDirection(i / (float)lineSteps));
lineStart = lineEnd;
}
}
Cubic Curve를 이용한 Bezier 선분 그리기
Cubic Curve곡선 공식 : B(t) = (1 - t)3 P0 + 3 (1 - t)2 t P1 + 3 (1 - t) t2 P2 + t3 P3
미분 방정식 : B'(t) = 3 (1 - t)2 (P1 - P0) + 6 (1 - t) t (P2 - P1) + 3 t2 (P3 - P2)
곡선에 접하는 직선의 방정식은 미분으로 구할 수 있음
Bezier.cs
public static Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t) {
// Clamp01 : 0 ~ 1 사이의 값으로 Clamp
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);
}
BezierCurves.cs
새로운 정점 추가
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)
};
}
public Vector3 GetVelocity (float t) {
return transform.TransformPoint(
CubicBezier.GetFirstDerivative(points[0], points[1], points[2], points[3], t)) - transform.position;
}
public Vector3 GetPoint (float t) {
return transform.TransformPoint(CubicBezier.GetPoint(points[0], points[1], points[2], points[3], t));
}
BezierCurveInspector.cs
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(p1, p2);
Handles.DrawLine (p2, p3);
Handles.Draw()를 이용한 Bezier 곡선 그리기
Bezier곡선은 Handles.DrawBezier()로 표현하고 기존의 스탭에 따른 기울기는 우리가 작성한 프로그램으로 표현할 수 있도록 함
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(p1, p2);
Handles.DrawLine (p2, p3);
ShowDirections ();
Handles.DrawBezier(p0, p3, p1, p2, Color.white, null, 2f);
}
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);
}
}
[ Splines ]
단순한 곡선이 아닌 복잡한 경로의 곡선에 대한 표현
Menu : GameObject >> Create Empty
Name : Spline
BezierSpline.cs 생성
BezierCurve.cs를 사용해서 만듬(클래스 명만 바꿈)
BezierSpline >> (to) Spline
BezierSplineInspector.cs 생성
BezierCurveInspcector.cs를 사용해서 만듬
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(BezierSpline))]
public class BezierSplineInspector : Editor {
private BezierSpline spline;
private Transform handleTransform;
private Quaternion handleRotation;
private const int lineSteps = 10;
private const float directionScale = 0.5f;
private void OnSceneGUI () {
spline = target as BezierSpline;
handleTransform = spline.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(p1, p2);
Handles.DrawLine(p2, p3);
ShowDirections ();
Handles.DrawBezier (p0, p3, p1, p2, Color.white, null, 2f);
}
private void ShowDirections() {
Handles.color = Color.green;
Vector3 point = spline.GetPoint (0f);
Handles.DrawLine (point, point + spline.GetDirection (0f) * directionScale);
for (int i = 1; i <= lineSteps; i++) {
point = spline.GetPoint(i / (float)lineSteps);
Handles.DrawLine(point, point + spline.GetDirection(i / (float)lineSteps) * directionScale);
}
}
private Vector3 ShowPoint (int index) {
Vector3 point = handleTransform.TransformPoint(spline.points[index]);
EditorGUI.BeginChangeCheck();
point = Handles.DoPositionHandle(point, handleRotation);
if (EditorGUI.EndChangeCheck()) {
Undo.RecordObject(spline, "Move Point");
EditorUtility.SetDirty(spline);
spline.points[index] = handleTransform.InverseTransformPoint(point);
}
return point;
}
}
BezierSpline.cs에 다른 커브를 추가하기
곡선이 연결되어야 되어야 하므로 곡선이 끝나는 끝 점과 추가로 3개의 점이 요구됨
using System;
...
public void AddCurve () {
Vector3 point = points[points.Length - 1];
Array.Resize(ref points, points.Length + 3);
point.x += 1f;
points[points.Length - 3] = point;
point.x += 1f;
points[points.Length - 2] = point;
point.x += 1f;
points[points.Length - 1] = point;
}
ref와 out : 기본 자료형의 매개 변수 전달 방식은 Call By Value 형식으로 전달되지만, ref와 out을 사용하면 참조 전달(Call by Reference)이 가능
ref는 메서드 내에서 변경된 값은 리턴 후 유효 ( 그 값을 참조하여 메소드에서 값을 바꾼다는 의미가 강함)
반드시 전달 전에 초기화가 되어 있어야 함
out는 메서드 내에서 지정된 값으로 할당되어 전달됨 ( 메소드에서 어떤 처리를 하고 그 값에 대한 결과를 받아 본다는 의미가 강함)
초기화되어 있을 필요가 없이 선언만 되어 있어도 됨
BezierSplineInspector.cs에 버튼을 추가해서 정점(3개)을 추가할 수 있도록 함
public override void OnInspectorGUI () {
DrawDefaultInspector();
spline = target as BezierSpline;
if (GUILayout.Button("Add Curve")) {
Undo.RecordObject(spline, "Add Curve");
spline.AddCurve();
EditorUtility.SetDirty(spline);
}
}
OnIspectorGUI() : 인스펙터에 자신의 커스텀 GUI를 추가하도록 한다.
DrawDefultInspector() : 자동으로 인스펙터를 그려내도록 하기 위해서 OnInspectorGUI로부터 이 함수를 호출(전체적인 인스펙터를 새로 그리기 보다는 단지 기본적인 모습을 그리고 버튼같은 단순한 것을 추가할 때 사용)
GUILayout.Button() : OnGUI() 함수나 OnInspectorGUI() 함수 내에서 GUI 버튼 생성 함수
BezierSplineInspector.cs에서 추가한 곡선을 시각적으로 보여주게 하기
private void OnSceneGUI () {
spline = target as BezierSpline;
handleTransform = spline.transform;
handleRotation = Tools.pivotRotation == PivotRotation.Local ?
handleTransform.rotation : Quaternion.identity;
Vector3 p0 = ShowPoint(0);
// (p0 ~ p3)까지의 베지어 곡선과 (p3 ~ p6)까의 베어 곡선을 만들고 있음
for (int i = 1; i < spline.points.Length; i += 3) {
Vector3 p1 = ShowPoint (i);
Vector3 p2 = ShowPoint (i + 1);
Vector3 p3 = ShowPoint (i + 2);
Handles.color = Color.gray;
Handles.DrawLine (p0, p1);
Handles.DrawLine (p2, p3);
Handles.DrawBezier (p0, p3, p1, p2, Color.white, null, 2f);
p0 = p3; // 새롭게 생성되는 베지어 곡선을 위해 마지막 정점을 시작점으로 대입
}
ShowDirections();
}
Direction을 표현할 수 있도록 하자
Bezier 곡선의 개수에 따라 적용될 수 있도록 함
BezierSpline.cs
// 추가된 3개의 정점마다 Curve의 개수를 의미
public int CurveCount {
get {
return (points.Length - 1) / 3;
}
}
BezierSpline.cs
t 값에 따른 처리 (기존의 1/10 ~ 10/20 에서 1/20 ~ 20/20 의 값들이 넘어 오는 것에 대한 처리: Curve가 2개 일때)
public Vector3 GetVelocity (float t) {
int i;
if (t >= 1f) {
t = 1f;
i = points.Length - 4;
}
else {
t = Mathf.Clamp01(t) * CurveCount;
i = (int)t;
t -= i;
i *= 3;
}
return transform.TransformPoint(
Bezier.GetFirstDerivative(points[i], points[i+1], points[i+2], points[i+3], t)) - transform.position;
}
public Vector3 GetDirection(float t) {
return GetVelocity (t).normalized;
}
// spline 커브가 2개로 되어 있다고 가정하고 봤을 때 .....
public Vector3 GetPoint (float t) { // t로 들어 올 수 있는 값 : 1/20, 2/20 ~ 20/20
int i;
if (t >= 1f) { // 20/20인 경우 맨 마지막 정점에 대한 처리를 해줌
t = 1f;
i = points.Length - 4;
}
else {
t = Mathf.Clamp01(t) * CurveCount; // 커브가 2개 이므로 커브당 10개의 step으로 진행하기 위해서
i = (int)t; // 1 ~ 9까지의 step에 i는 0이고, 10번째 step 부터는 i가 1
t -= i; // 10 이상이면 1을 빼줌(즉 t가 1.2가 되면 0.2가 되도록)
i *= 3; // points의 위치를 "3개의 정점 단위"(Curve)로 이동해서 첫 포인트를 설정
}
return transform.TransformPoint(Bezier.GetPoint(points[i], points[i+1], points[i+2], points[i+3], t));
}
BezierSplineInspector.cs
private const int stepsPerCurve = 10;
private const float directionScale = 0.5f;
private void ShowDirections() {
Handles.color = Color.green;
Vector3 point = spline.GetPoint (0f);
Handles.DrawLine (point, point + spline.GetDirection (0f) * directionScale);
int steps = stepsPerCurve * spline.CurveCount; // 10 * 2
for (int i = 1; i <= steps; i++) {
point = spline.GetPoint(i / (float)steps);
Handles.DrawLine(point, point + spline.GetDirection(i / (float)steps) * directionScale);
}
}
핸들(Handle) 정리하기
활성화된 정점의 Handle만 표시할 수 있도록 하고, 나머지 정점들은 흰색 정점으로 표현될 수 있도록 하기
BezierSplineInspector.cs
private const float handleSize = 0.04f;
private const float pickSize = 0.06f;
private int selectedIndex = -1;
private Vector3 ShowPoint (int index) {
// TransformPoint : 지역 좌계를 전역 좌표계로...
Vector3 point = handleTransform.TransformPoint(spline.points[index]);
Handles.color = Color.white;
// Handle에의해 그려지는 버튼
if (Handles.Button(point, handleRotation, handleSize, pickSize, Handles.DotCap)) {
selectedIndex = index;
}
if (selectedIndex == index) {
EditorGUI.BeginChangeCheck();
// Handle을 point에 붙여줌
point = Handles.DoPositionHandle(point, handleRotation);
if (EditorGUI.EndChangeCheck()) {
Undo.RecordObject(spline, "Move Point");
EditorUtility.SetDirty(spline);
// 이전의 기억하고 있던 point를 월드에서 지역 좌계로 환원해서 대입해 줌
spline.points[index] = handleTransform.InverseTransformPoint(point);
}
}
return point;
}
스크린에서 보여지는 정점의 크기를 항상 같은 크기로 보일 수 있도록 하기
// 월드 좌표계에서 고정된 Handle의 크기를 얻음
float size = HandleUtility.GetHandleSize(point);
Handles.color = Color.white;
// Handle에의해 그려지는 버튼을 생성
if (Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {
selectedIndex = index;
}
[ Constraining Control Points ]
첫번째 곡선과 두 번째 곡선이 서로 다른 기울기(정점에서의 Direction)를 가지고 있음
두개의 곡선이 원만하게 같은 기울기를 가지기 위해서는 첫번째 곡선의 마지막 정점을 기준으로 이전 정점( 첫번째 곡선의 세번째 정점)과 이후 정점(두번째 곡선의 첫 정점)이 대칭을 이루어야만 함
이렇게 하기 위해서 일단 가장 유연한 접근 법은 정점을 마음대로 움직일 수 있게 하는 것이 아니라 제한적인 움직임을 허용할 수 있도록 하는 것임 (정점 정보를 private으로 표현)
제한적인 운영 정점 만들기
BezierSpline.cs
[SerializeField] // 인스펙터에서 보일 수 있도록 하고 있음
private Vector3[] points;
public int ControlPointCount {
get {
return points.Length;
}
}
public Vector3 GetControlPoint (int index) {
return points[index];
}
public void SetControlPoint (int index, Vector3 point) {
points[index] = point;
}
BezierSplineInspector.cs
using UnityEditor;
using UnityEngine;
[CustomEditor(typeof(BezierSpline))]
public class BezierSplineInspector : Editor {
private BezierSpline spline;
private Transform handleTransform;
private Quaternion handleRotation;
private const int stepsPerCurve = 10;
private const float directionScale = 0.5f;
private void OnSceneGUI () {
spline = target as BezierSpline;
handleTransform = spline.transform;
handleRotation = Tools.pivotRotation == PivotRotation.Local ?
handleTransform.rotation : Quaternion.identity;
Vector3 p0 = ShowPoint(0);
// (p0 ~ p3)까지의 베지어 곡선과 (p3 ~ p6)까의 베어 곡선을 만들고 있음
for (int i = 1; i < spline.ControlPointCount; i += 3) {
Vector3 p1 = ShowPoint (i);
Vector3 p2 = ShowPoint (i + 1);
Vector3 p3 = ShowPoint (i + 2);
Handles.color = Color.gray;
Handles.DrawLine (p0, p1);
Handles.DrawLine (p2, p3);
Handles.DrawBezier (p0, p3, p1, p2, Color.white, null, 2f);
p0 = p3; // 새롭게 생성되는 베지어 곡선을 위해 마지막 정점을 시작점으로 대입
}
ShowDirections();
}
private void ShowDirections() {
Handles.color = Color.green;
Vector3 point = spline.GetPoint (0f);
Handles.DrawLine (point, point + spline.GetDirection (0f) * directionScale);
int steps = stepsPerCurve * spline.CurveCount; // 10 * 2
for (int i = 1; i <= steps; i++) {
point = spline.GetPoint(i / (float)steps);
Handles.DrawLine(point, point + spline.GetDirection(i / (float)steps) * directionScale);
}
}
private const float handleSize = 0.04f;
private const float pickSize = 0.06f;
private int selectedIndex = -1;
private Vector3 ShowPoint (int index) {
// TransformPoint : 지역 좌계를 전역 좌표계로...
Vector3 point = handleTransform.TransformPoint(spline.GetControlPoint(index));
// 월드 좌표계에서 고정된 Handle의 크기를 얻음
float size = HandleUtility.GetHandleSize(point);
Handles.color = Color.white;
// Handle에의해 그려지는 버튼을 생성
if (Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {
selectedIndex = index;
Repaint (); // Editor 인스펙터를 다시금 그릴 수 있도록 해줌
}
if (selectedIndex == index) {
EditorGUI.BeginChangeCheck();
// Handle을 point에 붙여줌
point = Handles.DoPositionHandle(point, handleRotation);
if (EditorGUI.EndChangeCheck()) {
Undo.RecordObject(spline, "Move Point");
EditorUtility.SetDirty(spline);
// 이전의 기억하고 있던 point를 월드에서 지역 좌계로 환원해서 대입해 줌
spline.SetControlPoint(index, handleTransform.InverseTransformPoint(point));
}
}
return point;
}
public override void OnInspectorGUI () {
//DrawDefaultInspector();
spline = target as BezierSpline;
if (selectedIndex >= 0 && selectedIndex < spline.ControlPointCount) {
DrawSelectedPointInspector();
}
if (GUILayout.Button("Add Curve")) {
Undo.RecordObject(spline, "Add Curve");
spline.AddCurve();
EditorUtility.SetDirty(spline);
}
}
private void DrawSelectedPointInspector() {
GUILayout.Label("Selected Point");
EditorGUI.BeginChangeCheck();
//Vector3Field : Vector3에 대한 x, y, z 값이 한줄로 Inpector에서 나올 수 있도록 표함
Vector3 point = EditorGUILayout.Vector3Field("Position", spline.GetControlPoint(selectedIndex));
if (EditorGUI.EndChangeCheck()) {
Undo.RecordObject(spline, "Move Point");
EditorUtility.SetDirty(spline);
spline.SetControlPoint(selectedIndex, point);
}
}
}
EditorGUiLayout.Vector3Field() : Vector3에 대한 x, y, z 값이 한줄 형태로 Inpector에서 나올 수 있도록 표함
Editor.Repaint() : Editor 인스펙터를 다시금 그릴 수 있도록 해줌
모든 정점이 표시되었던 것에서 선택된 정점의 정보만 보여질 수 있도록 하고 있음