Unity中的曲线绘制(一)——直线
直线
让我们从创建一条简单的直线开始。两点成线,我们需要一个起始点p0,以及终点p2;
现在我们可以创建一个游戏对象,为它绑定Line脚本,并且设置两个点的坐标。目前我们在scene视图中还看不见这些点。当我们选中Line对象时,希望能够像选中别的游戏对象一样,在scene视图中显示Line的形状。这点可以通过创建自定义的inspector来实现。
与Editor有关的脚本必须放在Editor文件夹下,在Assets目录下创建一个Editor文件夹,并且在它里面新建一个LineInspector脚本。
自定义的inspector必须继承UnityEditor.Editor。并且要为它添加UnityEditor.CustomEditor属性。这样Unity才知道选中Line的时候要使用我们自定义的editor。
1
2
3
4
5
6
using
UnityEditor;
using
UnityEngine;
[CustomEditor(
typeof
(Line))]
public
class
LineInspector : Editor {
}
要使editor起作用,必须为它添加OnSceneGUI方法。我们需要在这个方法里相关的代码来在scene视图中绘制元素。
Editor类有一个target变量,这个变量代表的是鼠标选中的对象(Hieraychy或者scene中)。我们把它的类型转换为Line,然后使用Handles工具类来在指定点之间绘制直线。
1
2
3
4
5
6
private
void
OnSceneGUI () {
Line line = target
as
Line;
Handles.color = Color.white;
Handles.DrawLine(line.p0, line.p1);
}
现在我们可以在scene视图中看见一条直线了,但是它是固定的,移动、旋转、缩放Line对象,这条直线是完全不会变的。这是由于Handles是在世界坐标系下进行操作,而我们的两个点的坐标位于相对于Line对象的局部坐标系。因此我们需要将p0和p1的坐标转换为世界坐标后再使用Handles工具类来绘制。
1
2
3
4
5
6
7
8
9
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的旋转。
1
2
3
4
5
6
7
8
9
10
11
12
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);
}
尽管现在已经可以看到点的坐标轴,但是目前并不能支持Unity的pivot rotation 模式。需要使用Tools.pivotRotation来判断目前的模式,对旋转进行转换。
1
2
Quaternion handleRotation = Tools.pivotRotation == PivotRotation.Local ?
handleTransform.rotation : Quaternion.identity;
平时使用Unity时,在scene视图中移动对象的坐标轴,对象的transform值就会发生改变。在这里,我们希望实现,移动两点的坐标轴,Line脚本中它们的值也会发生改变。同样地,由于坐标轴的坐标是在世界坐标系下,我们需要用InverseTransformPoint方法把坐标转换为Line脚本绑定对象的局部坐标系。为了实时反馈点位置的改变,我们需要使用EditorGUI.BeginChangeCheck方法和EditorGUI.EndChangeCheck方法。当p0对应坐标轴被我们拖动改变后,EditorGUI.EndChangeCheck()方法会返回true,此时就可以将其坐标进行转换,修改脚本中p0的值。
1
2
3
4
5
6
7
8
9
10
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);
}
现在我们可以在scene视图中拖动我们的直线和点了。
但是仍然有两个小问题。
1、不能撤销点的拖动(windows下为ctrl+z)。
2、Unity不会记录改变,退出的时候不会提示保存信息。
第一个问题解决方法是,在修改点的位置之前,调用Undo.RecordObject方法;第二个问题通过调用EditorUtility.SetDirty解决。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
Unity中的曲线绘制(二)——贝塞尔曲线
贝塞尔曲线
贝塞尔曲线是一种特殊的曲线,它由一系列的点所定义。它开始于第一点结束于最后一个点,但并不需要经过中间点。中间点只用于端点插值,使得曲线平滑。
右图为二次贝塞尔曲线演示动画,t在[0,1]区间,可以看到曲线从p0开始,到p2结束,但是并不经过p1.
新建一个BezierCurve类,它包含三个点,同样定义Reset方法来初始化点的值,当绑定了BezierCurve脚本的对象创建或重置时,Unity editor会自动调用Reset方法。这个类代表了一个二次贝塞尔曲线。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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)
};
}
}
同样地,我们为curve写一个inspector,它和LineInspector颇为类似。为了减少重复代码,我们把显示点的代码放在单独的ShowPoint方法里,通过传入参数index来调用。将curve,handleTransform和handleRotation作为全局变量,这样在调用ShowPoint时不用再传一次参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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;
}
}
贝塞尔曲线可以定义为一个参数方程。给定一个[0,1]区间内的参数t,就能得到曲线上的一个点,当t从0增至1,我们就能得到一条完整的贝塞尔曲线。
为了在场景中显示一条贝塞尔曲线,我们可以通过连续地画直线来模拟。做一个简单的循环,在点和点之间连续地绘制直线,并且将颜色设置为灰色。假设已经有GetPoint方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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里添加GetPoint方法,否则会编译报错。这一次要假设我们已经有一个工具类Bezier,对控制点进行插值处理。同样要将控制点转为世界坐标。
1
2
3
public
Vector3 GetPoint (
float
t) {
return
transform.TransformPoint(Bezier.GetPoint(points[0], points[1], points[2], t));
}
添加一个静态类Bezier,暂时忽略中间点,只对p0和p1做插值试试看。
1
2
3
4
5
public
static
class
Bezier {
public
static
Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2,
float
t) {
return
Vector3.Lerp(p0, p2, t);
}
}
当然,端点之间的线性插值完全忽略了中间点。那么,我们如何将中间点也考虑进来呢?答案是多次插值。第一步,在起点和中间点之间进行插值,第二步在中间点和终点之间进行插值。这样就得到了两个点。再对这两个点进行一次插值,得到的结果就是贝塞尔曲线上的一个点。
1
2
3
public
static
Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2,
float
t) {
return
Vector3.Lerp(Vector3.Lerp(p0, p1, t), Vector3.Lerp(p1, p2, t), t);
}
这种曲线又被称为二次贝塞尔曲线,因为它的数学表示是二次多项式。
线性贝塞尔曲线:
。
升一阶就可以得到
,即将线性贝塞尔曲线中的P0和P1换成了两个新的插值点。整理后可以得到二次贝塞尔曲线的数学表示:
。
根据上面的公式我们可以用二次多项式来代替原代码中的Vector3.Lerp。
既然已经得到了二次贝塞尔曲线的多项式表示,我们可以写出它的一阶导:
,添加相关方法。
1
2
3
4
5
public
static
Vector3 GetFirstDerivative (Vector3 p0, Vector3 p1, Vector3 p2,
float
t) {
return
2f * (1f - t) * (p1 - p0) +
2f * t * (p2 - p1);
}
曲线上某个点的一阶导就是该点的切线,这个方法返回的值可以认为是点沿曲线移动的速度,因此我们可以在BezierCurve中添加GetVelocity方法来获取速度。
因为它返回的是一个速度的向量而非一个点,它同样会受到曲线对象位置的影响,因此我们需要对它进行位置变换。
1
2
3
4
public
Vector3 GetVelocity (
float
t) {
return
transform.TransformPoint(Bezier.GetFirstDerivative(points[0], points[1], points[2], t)) -
transform.position;
}
为什么这里要用TransformPoint而不是TransformDirection呢?
因为TransformDirection方法不会考虑缩放的转换,但是我们需要把这个因素也考虑进去。因此我们把速度向量先当成一个点来处理,得到转换后的结果后再减去曲线对象的位置,就能得到正确的速度向量,即使缩放系数为负数,这个方法也会生效。
现在我们也绘制一下速度,同样在BezierCurveInspector中的OnSceneGUI方法中进行。
1
2
3
4
5
6
7
8
9
10
11
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;
}
我们可以很清楚地看到速度沿曲线变化的过程,但是这些线的长度太长,影响视图。我们改为只显示速度方向即可。
1
2
3
4
5
6
7
8
9
10
11
Vector3 lineStart = curve.GetPoint(0f);
Handles.color = Color.green;
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.GetDirection(i / (
float
)lineSteps));
lineStart = lineEnd;
}
在BezierCurve中添加GetDirection方法,对速度进行标准化。
1
2
3
public
Vector3 GetDirection (
float
t) {
return
GetVelocity(t).normalized;
}
接下来我们再升一阶,为Bezier添加新的方法,来处理三次贝塞尔曲线。和二次贝塞尔曲线类似,只不过它有四个点,其中两个控制点,两个起始点,共需要6次线性插值。整理后的曲线方程为:
,它的一阶导为
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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从二次升为三次贝塞尔曲线,为它添加第四个点,记得重置组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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)
};
}
BezierCurveInspector 同样也要进行更新,显示新添加的点。
1
2
3
4
5
6
7
8
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);
可以看到,我们利用直线绘制出来了初步的贝塞尔曲线,通过增加lineSteps的值,曲线会变得更为平滑。实际上,Unity已经封装了三次贝塞尔曲线的绘制方法 - Handles.DrawBezier
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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);
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);
}
}
Unity中的曲线绘制(三)——样条曲线
单独的一条贝塞尔曲线很容易生成,但是它并不能模拟更复杂的路径,但是我们可以通过组合不同的贝塞尔曲线,来得到复杂的曲线。复制BezierCurve的代码,修改为BezierSpline类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using
UnityEngine;
public
class
BezierSpline : MonoBehaviour {
public
Vector3[] points;
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;
}
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)
};
}
}
同样为它写一个editor,复制BezierCurveInspector中的代码,进行相应调整。然后我们就可以创建一个样条曲线对象并且在scene视图中编辑它了。
using UnityEditor;
using UnityEngine;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
[CustomEditor(
typeof
(BezierSpline))]
public
class
BezierSplineInspector : Editor {
private
const
int
lineSteps = 10;
private
const
float
directionScale = 0.5f;
private
BezierSpline spline;
private
Transform handleTransform;
private
Quaternion handleRotation;
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(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;
}
}
由于只是简单地复制了一下代码,现在的spline和之前的曲线并无不同。现在来为BezierSpline添加一个方法,用于为spline添加另外的曲线。由于spline必须是连续的,所以第一条曲线的终点就是第二条曲线的起点。要额外添加三个点。
1
2
3
4
5
6
7
8
9
10
11
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;
}
由于我们使用了Array.Resize来新建一个更大的数组来存放新的点。它是System namespace中的内置方法,所以在脚本的开头要声明;
1
2
using
UnityEngine;
using
System;
我们在spline的inspector里添加一个按钮,用于控制曲线的增加。我们可以重写Unity的OnInspectorGUI方法,来自定义自己的inspector。调用DrawDefaultInspector方法,然后用GUILayout方法绘制按钮,点击这个按钮可以为当前spline添加一段曲线。
1
2
3
4
5
6
7
8
9
public
override
void
OnInspectorGUI () {
DrawDefaultInspector();
spline = target
as
BezierSpline;
if
(GUILayout.Button(
"Add Curve"
)) {
Undo.RecordObject(spline,
"Add Curve"
);
spline.AddCurve();
EditorUtility.SetDirty(spline);
}
}
GUILayout.Button方法会绘制一个按钮并且返回它的点击状态
但是场景中还是只能看见一条曲线,调整BezierSplineInspector代码来显示所有曲线。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private
void
OnSceneGUI () {
spline = target
as
BezierSpline;
handleTransform = spline.transform;
handleRotation = Tools.pivotRotation == PivotRotation.Local ?
handleTransform.rotation : Quaternion.identity;
Vector3 p0 = ShowPoint(0);
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();
}
注意在OnInspectorGUI和OnSceneGUI里都分别引用了spline,这是因为这两个方法彼此独立,target可能会变化,所以最好分开声明一次。
现在可以看到所有的曲线都被绘制出来了,但是只有第一条曲线上有速度的标识。这是因为BezierSpline中的方法仍然只对四个点进行处理,所以要对它进行更改。
虽然有多段曲线,但是我们希望只使用一个[0,1]区间内的t来完成整个过程。思路是将t乘以曲线的段数,得到的结果取整数部分,就是曲线的索引。例如有三段曲线, t =0.6,那么乘以3之后得到1.8,整数部分为1,说明当前是在第二段曲线上。因为我们需要知道当前有几段曲线,所以添加一个CurveCount属性。
1
2
3
4
5
public
int
CurveCount {
get
{
return
(points.Length - 1) / 3;
}
}
上面说到将t乘以曲线的段数,得到的整数部分是曲线的索引,那么减去这个整数部分,得到的小数部分就是在当前曲线上插值的系数。当t等于1时,直接认为是最后一段曲线即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public
Vector3 GetPoint (
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.GetPoint(
points[i], points[i + 1], points[i + 2], points[i + 3], t));
}
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;
}
现在在整条样条曲线上面都能看见速度的指示线了,为了保证每一段贝塞尔曲线的速度线的数量一致,将BezierSplineInspector.ShowDirections方法进行一些调整,利用BezierSpline.CurveCount来决定每一段曲线的速度绘制数量。
1
2
3
4
5
6
7
8
9
10
11
12
private
const
int
stepsPerCurve = 10;
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;
for
(
int
i = 1; i <= steps; i++) {
point = spline.GetPoint(i / (
float
)steps);
Handles.DrawLine(point, point + spline.GetDirection(i / (
float
)steps) * directionScale);
}
}
为所有的点显示坐标轴,画面会变得很拥挤,我们可以只显示被选中的点的坐标轴,其他点用小按钮表示。更新ShowPoint方法,让它将控制点显示为小小的按钮,点击某个点时它的坐标轴将会显示。用一个参数selectedIndex代表当前选中按钮的index,默认为-1,代表初始状态没有点被选中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private
const
float
handleSize = 0.04f;
private
const
float
pickSize = 0.06f;
private
int
selectedIndex = -1;
private
Vector3 ShowPoint (
int
index) {
Vector3 point = handleTransform.TransformPoint(spline.points[index]);
Handles.color = Color.white;
if
(Handles.Button(point, handleRotation, handleSize, pickSize, Handles.DotCap)) {
selectedIndex = index;
}
if
(selectedIndex == 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;
}
现在控制点以白色方形按钮的形式呈现出来了,但是按钮的大小有一点奇怪,要么太大要么太小。我们希望它和坐标轴一样保证相同的屏幕尺寸。HandleUtility.GetHandleSize方法可以提供固定的屏幕尺寸。
1
2
3
4
5
float
size = HandleUtility.GetHandleSize(point);
Handles.color = Color.white;
if
(Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {
selectedIndex = index;
}
转http://gad.qq.com
Unity中的曲线绘制(一)——直线
现在我们可以创建一个游戏对象,为它绑定Line脚本,并且设置两个点的坐标。目前我们在scene视图中还看不见这些点。当我们选中Line对象时,希望能够像选中别的游戏对象一样,在scene视图中显示Line的形状。这点可以通过创建自定义的inspector来实现。
与Editor有关的脚本必须放在Editor文件夹下,在Assets目录下创建一个Editor文件夹,并且在它里面新建一个LineInspector脚本。
自定义的inspector必须继承UnityEditor.Editor。并且要为它添加UnityEditor.CustomEditor属性。这样Unity才知道选中Line的时候要使用我们自定义的editor。
1
2
3
4
5
6
|
using
UnityEditor;
using
UnityEngine;
[CustomEditor(
typeof
(Line))]
public
class
LineInspector : Editor {
}
|
要使editor起作用,必须为它添加OnSceneGUI方法。我们需要在这个方法里相关的代码来在scene视图中绘制元素。
Editor类有一个target变量,这个变量代表的是鼠标选中的对象(Hieraychy或者scene中)。我们把它的类型转换为Line,然后使用Handles工具类来在指定点之间绘制直线。
1
2
3
4
5
6
|
private
void
OnSceneGUI () {
Line line = target
as
Line;
Handles.color = Color.white;
Handles.DrawLine(line.p0, line.p1);
}
|
1
2
3
4
5
6
7
8
9
|
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的旋转。
1
2
3
4
5
6
7
8
9
10
11
12
|
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);
}
|
1
2
|
Quaternion handleRotation = Tools.pivotRotation == PivotRotation.Local ?
handleTransform.rotation : Quaternion.identity;
|
1
2
3
4
5
6
7
8
9
10
|
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);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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);
}
|
Unity中的曲线绘制(二)——贝塞尔曲线
贝塞尔曲线
贝塞尔曲线是一种特殊的曲线,它由一系列的点所定义。它开始于第一点结束于最后一个点,但并不需要经过中间点。中间点只用于端点插值,使得曲线平滑。
右图为二次贝塞尔曲线演示动画,t在[0,1]区间,可以看到曲线从p0开始,到p2结束,但是并不经过p1.
新建一个BezierCurve类,它包含三个点,同样定义Reset方法来初始化点的值,当绑定了BezierCurve脚本的对象创建或重置时,Unity editor会自动调用Reset方法。这个类代表了一个二次贝塞尔曲线。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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)
};
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
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;
}
}
|
贝塞尔曲线可以定义为一个参数方程。给定一个[0,1]区间内的参数t,就能得到曲线上的一个点,当t从0增至1,我们就能得到一条完整的贝塞尔曲线。
为了在场景中显示一条贝塞尔曲线,我们可以通过连续地画直线来模拟。做一个简单的循环,在点和点之间连续地绘制直线,并且将颜色设置为灰色。假设已经有GetPoint方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
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;
}
}
|
1
2
3
|
public
Vector3 GetPoint (
float
t) {
return
transform.TransformPoint(Bezier.GetPoint(points[0], points[1], points[2], t));
}
|
1
2
3
4
5
|
public
static
class
Bezier {
public
static
Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2,
float
t) {
return
Vector3.Lerp(p0, p2, t);
}
}
|
当然,端点之间的线性插值完全忽略了中间点。那么,我们如何将中间点也考虑进来呢?答案是多次插值。第一步,在起点和中间点之间进行插值,第二步在中间点和终点之间进行插值。这样就得到了两个点。再对这两个点进行一次插值,得到的结果就是贝塞尔曲线上的一个点。
1
2
3
|
public
static
Vector3 GetPoint (Vector3 p0, Vector3 p1, Vector3 p2,
float
t) {
return
Vector3.Lerp(Vector3.Lerp(p0, p1, t), Vector3.Lerp(p1, p2, t), t);
}
|
这种曲线又被称为二次贝塞尔曲线,因为它的数学表示是二次多项式。
线性贝塞尔曲线:
。
升一阶就可以得到
,即将线性贝塞尔曲线中的P0和P1换成了两个新的插值点。整理后可以得到二次贝塞尔曲线的数学表示:
。
根据上面的公式我们可以用二次多项式来代替原代码中的Vector3.Lerp。
既然已经得到了二次贝塞尔曲线的多项式表示,我们可以写出它的一阶导:
,添加相关方法。
1
2
3
4
5
|
public
static
Vector3 GetFirstDerivative (Vector3 p0, Vector3 p1, Vector3 p2,
float
t) {
return
2f * (1f - t) * (p1 - p0) +
2f * t * (p2 - p1);
}
|
曲线上某个点的一阶导就是该点的切线,这个方法返回的值可以认为是点沿曲线移动的速度,因此我们可以在BezierCurve中添加GetVelocity方法来获取速度。
因为它返回的是一个速度的向量而非一个点,它同样会受到曲线对象位置的影响,因此我们需要对它进行位置变换。
1
2
3
4
|
public
Vector3 GetVelocity (
float
t) {
return
transform.TransformPoint(Bezier.GetFirstDerivative(points[0], points[1], points[2], t)) -
transform.position;
}
|
为什么这里要用TransformPoint而不是TransformDirection呢?
因为TransformDirection方法不会考虑缩放的转换,但是我们需要把这个因素也考虑进去。因此我们把速度向量先当成一个点来处理,得到转换后的结果后再减去曲线对象的位置,就能得到正确的速度向量,即使缩放系数为负数,这个方法也会生效。
现在我们也绘制一下速度,同样在BezierCurveInspector中的OnSceneGUI方法中进行。
1
2
3
4
5
6
7
8
9
10
11
|
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;
}
|
我们可以很清楚地看到速度沿曲线变化的过程,但是这些线的长度太长,影响视图。我们改为只显示速度方向即可。
1
2
3
4
5
6
7
8
9
10
11
|
Vector3 lineStart = curve.GetPoint(0f);
Handles.color = Color.green;
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.GetDirection(i / (
float
)lineSteps));
lineStart = lineEnd;
}
|
1
2
3
|
public
Vector3 GetDirection (
float
t) {
return
GetVelocity(t).normalized;
}
|
接下来我们再升一阶,为Bezier添加新的方法,来处理三次贝塞尔曲线。和二次贝塞尔曲线类似,只不过它有四个点,其中两个控制点,两个起始点,共需要6次线性插值。整理后的曲线方程为:
,它的一阶导为
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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
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)
};
}
|
1
2
3
4
5
6
7
8
|
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);
|
可以看到,我们利用直线绘制出来了初步的贝塞尔曲线,通过增加lineSteps的值,曲线会变得更为平滑。实际上,Unity已经封装了三次贝塞尔曲线的绘制方法 - Handles.DrawBezier
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
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);
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);
}
}
|
Unity中的曲线绘制(三)——样条曲线
单独的一条贝塞尔曲线很容易生成,但是它并不能模拟更复杂的路径,但是我们可以通过组合不同的贝塞尔曲线,来得到复杂的曲线。复制BezierCurve的代码,修改为BezierSpline类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
using UnityEngine;
public class BezierSpline : MonoBehaviour {
public Vector3[] points;
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;
}
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)
};
}
}
|
同样为它写一个editor,复制BezierCurveInspector中的代码,进行相应调整。然后我们就可以创建一个样条曲线对象并且在scene视图中编辑它了。
using UnityEditor;
using UnityEngine;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
[CustomEditor( typeof (BezierSpline))]
public class BezierSplineInspector : Editor {
private const int lineSteps = 10;
private const float directionScale = 0.5f;
private BezierSpline spline;
private Transform handleTransform;
private Quaternion handleRotation;
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(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;
}
}
|
由于只是简单地复制了一下代码,现在的spline和之前的曲线并无不同。现在来为BezierSpline添加一个方法,用于为spline添加另外的曲线。由于spline必须是连续的,所以第一条曲线的终点就是第二条曲线的起点。要额外添加三个点。
1
2
3
4
5
6
7
8
9
10
11
|
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;
}
|
1
2
|
using UnityEngine;
using System;
|
1
2
3
4
5
6
7
8
9
|
public override void OnInspectorGUI () {
DrawDefaultInspector();
spline = target as BezierSpline;
if (GUILayout.Button( "Add Curve" )) {
Undo.RecordObject(spline, "Add Curve" );
spline.AddCurve();
EditorUtility.SetDirty(spline);
}
}
|
但是场景中还是只能看见一条曲线,调整BezierSplineInspector代码来显示所有曲线。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
private void OnSceneGUI () {
spline = target as BezierSpline;
handleTransform = spline.transform;
handleRotation = Tools.pivotRotation == PivotRotation.Local ?
handleTransform.rotation : Quaternion.identity;
Vector3 p0 = ShowPoint(0);
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();
}
|
现在可以看到所有的曲线都被绘制出来了,但是只有第一条曲线上有速度的标识。这是因为BezierSpline中的方法仍然只对四个点进行处理,所以要对它进行更改。
虽然有多段曲线,但是我们希望只使用一个[0,1]区间内的t来完成整个过程。思路是将t乘以曲线的段数,得到的结果取整数部分,就是曲线的索引。例如有三段曲线, t =0.6,那么乘以3之后得到1.8,整数部分为1,说明当前是在第二段曲线上。因为我们需要知道当前有几段曲线,所以添加一个CurveCount属性。
1
2
3
4
5
|
public int CurveCount {
get {
return (points.Length - 1) / 3;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public Vector3 GetPoint ( 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.GetPoint(
points[i], points[i + 1], points[i + 2], points[i + 3], t));
}
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;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
private const int stepsPerCurve = 10;
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;
for ( int i = 1; i <= steps; i++) {
point = spline.GetPoint(i / ( float )steps);
Handles.DrawLine(point, point + spline.GetDirection(i / ( float )steps) * directionScale);
}
}
|
为所有的点显示坐标轴,画面会变得很拥挤,我们可以只显示被选中的点的坐标轴,其他点用小按钮表示。更新ShowPoint方法,让它将控制点显示为小小的按钮,点击某个点时它的坐标轴将会显示。用一个参数selectedIndex代表当前选中按钮的index,默认为-1,代表初始状态没有点被选中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private const float handleSize = 0.04f;
private const float pickSize = 0.06f;
private int selectedIndex = -1;
private Vector3 ShowPoint ( int index) {
Vector3 point = handleTransform.TransformPoint(spline.points[index]);
Handles.color = Color.white;
if (Handles.Button(point, handleRotation, handleSize, pickSize, Handles.DotCap)) {
selectedIndex = index;
}
if (selectedIndex == 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;
}
|
现在控制点以白色方形按钮的形式呈现出来了,但是按钮的大小有一点奇怪,要么太大要么太小。我们希望它和坐标轴一样保证相同的屏幕尺寸。HandleUtility.GetHandleSize方法可以提供固定的屏幕尺寸。
1
2
3
4
5
|
float size = HandleUtility.GetHandleSize(point);
Handles.color = Color.white;
if (Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {
selectedIndex = index;
}
|
转http://gad.qq.com