Unity3D教你制作Bezier和Spine曲线编辑器三

原创 2017年09月04日 13:56:12

继续接着介绍曲线编辑器的制作,上篇博客介绍了关于Bezier曲线的制作,接下来给读者介绍Spine B样条曲线之作。

如果要创建复杂的曲线,我们需要连接多个曲线,这样的构造称为样条。让我们通过复制Bezier曲线代码来创建一个,将类型更改为BezierSpline。

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

另外我们还通过复制和调整Bezier曲线Inspector的代码来创建一个编辑器,然后我们可以创建一个spline对象并编辑它,就像一条曲线。

using UnityEditor;
using UnityEngine;

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


这样,让我们向BezierSpline添加一个方法,以向样条添加另一条曲线,因为我们希望spline是连续的,上一条曲线的最后一点与下一条曲线的第

一个点是一样的。所以每条额外的曲线又增加了三个点。

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命名空间内,因此我们应该声明我们在脚本

的顶部使用它。

using UnityEngine;
using System;

为了能够添加一条曲线,我们必须在spline的检查器中添加一个按钮,我们可以通过重写BezierSplineInspector的OnInspectorGUI方法来定

制组件的统一使用。注意,这不是一个特殊的统一方法,它依赖于继承。

另外,我们调用DrawDefaultInspector方法。然后我们使用GUILayout来绘制一个按钮,点击添加一条曲线。

	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,让它在所有曲线上循环。

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乘以曲线的数量然后丢弃分数。让我们

添加一个曲率属性来简化它。

	public int CurveCount {
		get {
			return (points.Length - 1) / 3;
		}
	}

之后,我们可以将t减少到小数部分来得到曲线的内插值。为了得到实际的点,我们必须将曲线指数乘以3。

然而,当初始t = 1时,这将会失败。在这种情况下,我们可以把它设置成最后一条曲线。

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,显示方向,所以它使用BezierSpline,曲率决定画多少线。

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,这样它显示一个按钮而不是一个位置句柄。这个按钮看起来像一个白色的点,当点击将会变成活动的点。

然后,如果point的索引与所选的索引匹配,那么我们只显示该位置句柄,这是我们在- 1中初始化的,所以默认情况下没有选择。

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保理。这个方法为我们提供

了一个固定的屏幕尺寸,在世界范围内的任何一点。

		float size = HandleUtility.GetHandleSize(point);
		Handles.color = Color.white;
		if (Handles.Button(point, handleRotation, size * handleSize, size * pickSize, Handles.DotCap)) {
			selectedIndex = index;
		}



代码下载地址:链接:http://pan.baidu.com/s/1gfrJVrl  密码:ft1o 中的编号03的包。

在下篇博客中介绍控制点的制作。。。。

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

websocket原理

一、websocket与httpWebSocket是HTML5出的东西(协议),也就是说HTTP协议没有变化,或者说没关系,但HTTP是不支持持久连接的(长连接,循环连接的不算) 首先HTTP有 1...

netty实战-自定义解码器处理半包消息

概述在李林锋的Netty系列之Netty编解码框架分析中介绍了各种解码器,也推荐组合LengthFieldBasedFrameDecoder ByteToMessageDecoder这两个解码器来处理...

Spring Cloud在国内中小型公司能用起来吗?

今天吃完饭休息的时候瞎逛知乎,突然看到这个一个问题Spring Cloud在国内中小型公司能用起来吗?,吸引了我的注意。仔细的看了题主的问题,发现这是一个好问题,题主经过了一番思考,并且用图形全面的将...

八皇后问题(递归,回溯)

八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八...

谈谈DOMContentLoaded:Javascript中的domReady引入机制

一、扯淡部分 回想当年,在摆脱写页面时js全靠从各种DEMO中copy出来然后东拼西凑的幽暗岁月之后,毅然决然地打算放弃这种处处“拿来主义”的不正之风,然后开启通往高大上的“前端攻城狮”的飞升之旅。想...

JVM内存模型及String对象内存分配

JVM内存模型及String对象内存分配总结了JVM内存模型,并用String与StringBuffer对象的内存分配进行事例分析说明,以便进一步理解JVM内存模型

73. Spring Data JPA方法定义规范

事情的起因:有人问过我们这个这个问题:为什么我利用Spring data jpa写的方法没有按照我想要的情况进行执行呢?我记得当时只是告诉他你你先看看Spring Data的命名规则吧。所以在这一小...

【面经笔记】Windows下的动态链接(DLL)

使用DLL的优点共享、模块化,可方便的组合,重用,升级基地址和RVA当一个PE文件装载时,其进程地址空间中的起始地址就是基地址,对于可执行文件exe,一般为0x400000,对于DLL文件一般为0x1...

Redis 数据库操作、配置以及慢查询

Redis 提供了几个面向 Redis 数据库的操作,例如之前已经介绍过或者使用过的 DBSIZE 、SELECT 、FLUSHDB/FLUSHALL 本节将通过具体的使用场景介绍这些命令。切换数据库...

深入理解Java虚拟机——JVM垃圾回收机制和垃圾收集器详解

一:概述说起垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来。在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,顾名思义,垃圾回收就是释放...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)