Dynamically Subdivide a Cubic Bezier Curve

Started by
4 comments, last by nonoptimalrobot 10 years, 8 months ago

Hi,

Am trying to build a class in unity to render a cubic bezier curve. I want to be able to add more curves and subdivide a given curve on the fly.

I have tried using the "Vectrosity" plugin but haven't been able to figure out how to dynamically subdivide a given curve.

If any body has any ideas, help please. :)

Thank you for any input.

Regards,

Andre.

Advertisement

Hi,

for the planar case I tried a method called "Chaikin's corner cutting scheme" to subdivide a polygonal path,

which converges to the quadric b-spline defined by the path.

See for instance this link. I attached an image as an example, just because it looks really nice.

Subdivision schemes like this are really easy to implement and you don't have to worry about math.

There are, however, certain limitations of subdivision schemes. For my purposes I needed to regularly

texture the curve. I wanted to place little dots on the curve with constant spacing between them which

I was unable to do using a subdivided mesh.

So I found it easier to work with the analytical representation of a bezier curve, ie. with it's control and

endpoints. You then build your mesh by sampling the curve. Here's a great resources for the "programmer's math"

of bezier curves!

If you are just interested in cubic bezier curves you can simplify the math in a lot of places.

"Subdividing" a curve is then a matter of increasing the sampling rate.

Hey,

I have implemented a scene, where I am able to create a cubic bezier curve. My problem is trying to further subdivide the given curve on the fly.

For example, if I have a spline, and I want to tweak the curve at a certain point. I would like to be able to add a handle or control point there and tweak only that

small segment of the curve.

In a more laments terms I want to create my own pen tool (similar to that of photoshop), within unity. This will specifically be used to help design a level.

Thanks though.

Cheers!!

Okay, here is an idea for implementing a pen tool, although I have not tested it myself:

Using quadric bezier curves, you want to maintain a sequence of points e c e c e c e c e .... where

e is an endpoint and c is a controlpoint of a quadric bezier curve.

The idea is that for ... e0 c1 e2 c3 e4 ... you have two bezier curves defined by (e0 c1 e2) and (e2 c3 e4)

that share a common endpoint (e2). So the line you are drawing with your tool really consists of many

beziercurves (segments) that you stitch together.

Look at this picture of a typical pen tool.

The points on the curve are the endpoints. The little handles next to each point on a curve are the controlpoints.

Ok here is the problem am facing, I have an array of points of a bezier curve say {p0, c0}, {p1, c1}, {p2, c2}, {p3, c3}, where p are points, and c are the control points when in reality all 4 serve as points which form the curve.

Now this gives me a spline from p0 to p3. The array is populated with all the vertices which make up the spline, and am using a time step of say 60. So that gives me 60 vertices. Now in an update, or on the push of a button, or clicking on a vertex on the spline, I want to make that a control point on the line. So basically split the curve into two parts. In theory it is simply stiching two bezier curves together.

But am having some issues in the way that I populate the array, with the subdivisions made.

This is my code in C#, done in Unity. The drawing of the line and the bezier function is within the plugin "Vectrosity".

The MakeCurve function is a Vectrosity function and takes in the four vertices, and step (segments).

Everything executes fine. But when I change the line with the subdivisions. Things get weird and freaky.


using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using Vectrosity;

public class Curve : MonoBehaviour {
	
	public static Curve use ;
	public static Camera m_mainCam ;
	
	public Material m_lineMaterial ;
	public GameObject m_anchorPrefab ;
	public GameObject m_controlPrefab ;
	
	private int oldWidth ;
	private int m_maxSegments = 60 ;
	private int m_minSegments = 12 ;
	private int pointIndex = 0 ;
	
	private VectorLine m_line ;
	private VectorLine m_controlLine ;	
	private Vector2[] m_linePoints ;
	
	// Used to hold vertices (where t == m_segments + 1)
	// For drawing a smoother curve
	private List<Vector2> m_curveAnchorPoints ;
	private List<Vector2> m_curveControlPoints ;
	
	// Used to hold vertices (where t < m_segments + 1)
	// For instantiating anchor and control points 
	private List<Vector2> m_curveInterpolatedPoints ;
	private List<Vector2> m_curveInterpolatedControlPoints ;
	
	// For drawing the control lines
	private Vector2[] m_drawCurvePoints ;
	private Vector2[] m_drawCurveControlPoints ;
	private Vector2[] m_drawPoints ;
	
	private GameObject m_prevAnchorObject = null ;
	
	void Start() 
	{
		Intialize();
	}
	
	void Update() 
	{	
		if (Screen.width != oldWidth) 
		{
			oldWidth = Screen.width;
			ChangeResolution();
		}
	}	
	
	private void Intialize()
	{
		// Intialize Main Camera
		m_mainCam = Camera.main;
		
		// Initalize Curve Instance
		use = this;
		
		// Make the line object for the curve
		m_linePoints = new Vector2[m_maxSegments+1] ;
		
		SetLine() ;
		
		// Set up initial curve points (Anchor & Control Points) 
		m_curveAnchorPoints = new List<Vector2>() ;
		m_curveAnchorPoints.Clear() ;
		
		m_curveControlPoints = new List<Vector2>() ;
		m_curveControlPoints.Clear() ;

		m_curveInterpolatedPoints = new List<Vector2>();
		m_curveInterpolatedPoints.Clear();
		
		m_curveInterpolatedControlPoints = new List<Vector2>();
		m_curveInterpolatedControlPoints.Clear();		
		
		m_curveAnchorPoints.Add(new Vector2(Screen.width * 0.25f, Screen.height * 0.25f)) ;
		m_curveControlPoints.Add(new Vector2(Screen.width * 0.125f, Screen.height * 0.5f)) ;
		
		m_curveAnchorPoints.Add(new Vector2(Screen.width - Screen.width * 0.25f, Screen.height - Screen.height * 0.25f)) ;
		m_curveControlPoints.Add(new Vector2(Screen.width - Screen.width * 0.125f, Screen.height * 0.5f)) ;
		
		m_line.MakeCurve(m_curveAnchorPoints[0], m_curveControlPoints[0], m_curveAnchorPoints[1], m_curveControlPoints[1], m_maxSegments) ;

		DrawLine(m_line) ;		
		
		// Get vertices for drawing min anchor and control points
		for(int i = 0; i < m_maxSegments + 1; i += m_minSegments)
		{
			m_curveInterpolatedPoints.Add(m_line.points2[i]) ;
			m_curveInterpolatedControlPoints.Add(new Vector2(m_line.points2[i].x + 50f, m_line.points2[i].y + 5f)) ;
		}
		
//		for(int i = 0; i < m_curveInterpolatedPoints.Count; i++)
//		{
//			Debug.Log(i + 1 + "th " + "Curve Point - " + m_curveInterpolatedPoints[i] + "\n" + "& Control Point - " + m_curveInterpolatedControlPoints[i]) ;
//		}
		
		SetDrawingArray();
		m_controlLine = new VectorLine("Control Line", m_drawPoints, Color.red, m_lineMaterial, 2.0f) ;
		
		DrawLine(m_controlLine) ;				
	}
	
	private void SetLine() 
	{
		m_line = new VectorLine("Curve", m_linePoints, m_lineMaterial, 5.0f, LineType.Continuous) ;
		m_line.depth = 1 ;	
	}
	
	private void DrawLine(VectorLine line)
	{
		line.Draw() ;	
	}
	
	private void SetDrawingArray()
	{
		int len = m_curveInterpolatedPoints.Count + m_curveInterpolatedControlPoints.Count;
		m_drawPoints = new Vector2[len];
		
		m_drawCurvePoints = m_curveInterpolatedPoints.ToArray();
		m_drawCurveControlPoints = m_curveInterpolatedControlPoints.ToArray();
		
		int j = 0;
		for(int i = 0; i < len; i += 2)
		{
			m_drawPoints[i] = m_drawCurvePoints[j];
			m_drawPoints[i + 1] = m_drawCurveControlPoints[j];
			
			j++;
		}
		
		for(int i = 0; i < len; i += 2)
		{
			AddControlObjects();
		}

//		for(int i = 0; i < len; i += 2)
//		{
//			Debug.Log("Curve Point - " + m_drawPoints[i] + "& Control Point - " + m_drawPoints[i + 1]) ;
//		}
	}
	
	private void AddControlObjects() 
	{
		GameObject anchorObject = Instantiate(m_anchorPrefab, m_mainCam.ScreenToViewportPoint(m_drawPoints[pointIndex]), Quaternion.identity) as GameObject ;
		anchorObject.GetComponent<CurveControl>().m_objectNumber = pointIndex++ ;
		
		GameObject controlObject = Instantiate(m_controlPrefab, m_mainCam.ScreenToViewportPoint(m_drawPoints[pointIndex]), Quaternion.identity) as GameObject ;
		controlObject.GetComponent<CurveControl>().m_objectNumber = pointIndex++ ;

		anchorObject.GetComponent<CurveControl>().m_controlObject01 = controlObject ;
		anchorObject.GetComponent<CurveControl>().m_controlObject02 = m_prevAnchorObject ;
		
		m_prevAnchorObject = anchorObject ;
	}	
	
	public void UpdateLine(int objectNumber, Vector2 pos, GameObject go)
	{
		// Get previous position, so we can make the control point move with the anchor point
		Vector2 oldPos = m_drawPoints[objectNumber] ;	
		m_drawPoints[objectNumber] = pos ;
		
		int curveNumber = objectNumber / 4;
		int curveIndex = curveNumber * 4;

		m_line.MakeCurve(m_drawPoints[curveIndex], m_drawPoints[curveIndex + 1], m_drawPoints[curveIndex + 2], m_drawPoints[curveIndex + 3], m_minSegments, (m_minSegments + 1) * curveNumber);		
		
		// If it's an anchor point...
		if (objectNumber % 2 == 0) 
		{
			// Move control point also
			m_drawPoints[objectNumber + 1] += pos - oldPos ;
			go.GetComponent<CurveControl>().m_controlObject01.transform.position = m_mainCam.ScreenToViewportPoint(m_drawPoints[objectNumber + 1]) ;

			// If it's not an end anchor point, move the next anchor/control points as well, and update the next curve
		 	if (objectNumber > 0 && objectNumber < m_drawPoints.Length - 4) 
			{
				m_drawPoints[objectNumber + 2] = pos ;
				m_drawPoints[objectNumber + 3] += pos - oldPos ;
				go.GetComponent<CurveControl>().m_controlObject02.transform.position = m_mainCam.ScreenToViewportPoint(m_drawPoints[objectNumber + 3]) ;					
				
				m_line.MakeCurve(m_drawPoints[curveIndex + 4], m_drawPoints[curveIndex + 5], m_drawPoints[curveIndex + 6], m_drawPoints[curveIndex + 7], m_minSegments, (curveNumber + 1) * (m_minSegments + 1));				
			}
		}
		
		DrawLine(m_line) ;
		DrawLine(m_controlLine) ;
	}	
	
	private void ChangeResolution() 
	{
		VectorLine.SetCamera();
		DrawLine(m_line);
		DrawLine(m_controlLine);
		
		var controlPointObjects = GameObject.FindGameObjectsWithTag("GameController");
		
		foreach (var obj in controlPointObjects) 
		{
			obj.transform.position = m_mainCam.ScreenToViewportPoint(m_drawPoints[obj.GetComponent<CurveControl>().m_objectNumber]);
		}
	}	
}

If you want to break a single curve into two distinct curves you need to derive the position of additional control points such that the union of the two curves trace the same space as the original curve. It looks like you are using points on the original curve as anchors AND controls points; this won't work, you need to compute new control points. The mathematics required to do this depends on now Vectrosity implements VectorLine::MakeCurve(...). My guess is that it uses a cubic Bezier curve but it might use a slight variant where the control points specify directional derivatives or maybe even something more ad-hoc. This link might help shed some light on how splines work:

Intro to Cubic Splines

If you know Vectrosity uses Cubic Bezier Curves then here's s reasonable explanation on how to turn a single curve into two curves such that they trace the same space:

how-to-split-a-cubic-bezier-curve

[ edit ] That second link doesn't contain an explicit explanation but it should set you on the right path.

This topic is closed to new replies.

Advertisement