flopoloco's picture

Path and road generator

This is a research I made for generating paths and roads. I got tired of all those complicated and useless road generators, so I decided to make a very simple on my own. Probably it will be used in some future games I will make (mostly in Unity, that will be ported to).

I guess that it might be also very useful to use it in your games too, so grab it here study it and learn from it.

Room for improvments:
1. Smooth curve calculation: Bezier algorithm seem to suck a bit, I will try a different algorithm for even distribution.
2. Find a way to control the width of the path (on each cuve point) to add narrow or wider varieties.
3. Add UVs for textures
4. Add support for N handle points (now only limited to 4)
5. Add support for enclosed curve paths

using System;
using System.Collections.Generic;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
 
namespace ObjectOnBezier
{
	public class Program : GameWindow
	{
		Matrix4 matrixProjection, matrixModelview;
		Random rand = new Random();
 
		#region Path properties
		/// <summary>How many segments road has.</summary>
		int pathSegments = 20;
		/// <summary>The handles of the curve.</summary>
		Vector3[] handles = new Vector3[4];
		/// <summary>Generated path that is based on handles.</summary>
		Vector3[] path;
		/// <summary>The actual vertex geometry of the path.</summary>
		Vector3[] geometry;
		#endregion
 
		/// <summary>This is the player movement along the path (added for test purposes only)</summary>
		float cycle = 0f;
 
 
		protected override void OnLoad(EventArgs e)
		{
			Width = 1024;
			Height = 768;
			GL.ClearColor(Color.DarkRed);
			GL.Enable(EnableCap.DepthTest);
 
			// Length of the path (for display purposes)
			float size = 25f;
			for (int i = 0; i < handles.Length; i++)
				handles[i] = new Vector3(0f, 0f, size - (i * size/2f));
 
			path = new Vector3[pathSegments];
			geometry = new Vector3[pathSegments * 2];
		}
 
		protected override void OnResize(EventArgs e)
		{
			GL.Viewport(0, 0, Width, Height);
			matrixProjection = Matrix4.CreatePerspectiveFieldOfView(
				(float)Math.PI / 4, Width / (float)Height, 1f, 1000f);
			GL.MatrixMode(MatrixMode.Projection);
			GL.LoadMatrix(ref matrixProjection);
		}
 
		protected override void OnUpdateFrame(FrameEventArgs e)
		{
			// When player reaches and of the path then reset his position and change positions of handles
 
			cycle += (float)e.Time * 1f;
 
			if (cycle > 1f)
			{
				cycle = 0f;
 
				for (int i = 0; i < 4; i++)
					handles[i].X = (float)rand.Next(-10, 10);
			}
		}
 
		protected override void OnRenderFrame(FrameEventArgs e)
		{
			#region Camera
			GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
			matrixModelview = Matrix4.LookAt(0f, 30f, -30f, 0f, 0f, 0f, 0f, 1f, 0f);
			GL.MatrixMode(MatrixMode.Modelview);
			GL.LoadMatrix(ref matrixModelview);
			#endregion
 
			#region Draw point handles (yellow large points)
			GL.PointSize(10f);
			GL.Color3(Color.Yellow);
			GL.Begin(BeginMode.Points);
			for (int i = 0; i < 4; i++) GL.Vertex3(handles[i]);
			GL.End();
			#endregion
 
			#region Process path curve
			// Calculate positions points
			for (int i = 0; i < pathSegments; i++)
			{
				float t = i / (float) pathSegments;
				path[i] = CalculateBezierPoint(
					t, handles[0], handles[1], handles[2], handles[3]);
			}
 
			// Draw line of the curve path
			GL.PointSize(5f);
			GL.Color3(Color.Green);
			GL.Begin(BeginMode.LineStrip);
			for (int i = 0; i < pathSegments; i++) GL.Vertex3(path[i]);
			GL.End();
 
			// Draw segment points (to see how the cuve path is divided)
			GL.PointSize(2f);
			GL.Color3(Color.White);
			GL.Begin(BeginMode.Points);
			for (int i = 0; i < pathSegments; i++) GL.Vertex3(path[i]);
			GL.End();
			#endregion
 
			#region Process path geometry
			// Calculate geometry
			for (int i=0; i < pathSegments; i++)
			{
				Vector3 normal = new Vector3(0f);
 
				// Note: Because we need to look ahead 1 array index, we make
				// sure that we do not exceed limits of path[i] array.
				if (i < pathSegments - 1)
				{
					// Normal calculation: nx = by - ay, ny = -(bx - ax)
					normal = new Vector3(
						-(path[i+1].Z - path[i].Z), 0f, path[i+1].X - path[i].X
					);
 
					normal.Normalize();
				}
 
				// Store left extrusion (calculated normal + point position)
				geometry[i*2] = normal + path[i];
				// Store right extrusion (flipped calculated normal + point position)
				geometry[(i*2)+1] = (normal * -1f) + path[i];
			}
 
			// Draw geometry (only for test purposes) 
			GL.PointSize(2f);
			GL.Color3(Color.Black);
			GL.Begin(BeginMode.Lines);
 
			// Don't ask: After of lots expirementation
			for (int i = 0; i < geometry.Length-4; i+=2)
			{
				// Left
				GL.Vertex3(geometry[i+0]);
				GL.Vertex3(geometry[i+2]);
 
				// Right
				GL.Vertex3(geometry[i+1]);
				GL.Vertex3(geometry[i+3]);
 
				// Bottom
				GL.Vertex3(geometry[i+2]);
				GL.Vertex3(geometry[i+3]);
 
				// Upper
				GL.Vertex3(geometry[i]);
				GL.Vertex3(geometry[i+1]);
 
			}
			GL.End();
 
			// Draw points
			GL.PointSize(3f);
			GL.Color3(Color.Orange);
			GL.Begin(BeginMode.Points);
			for (int i = 0; i < geometry.Length; i++) GL.Vertex3(geometry[i]);
			GL.End();
			#endregion
 
			#region Draw moving object
			GL.PointSize(15f);
			GL.Color3(Color.Pink);
			GL.Begin(BeginMode.Points);
			GL.Vertex3(CalculateBezierPoint(cycle, handles[0], handles[1], handles[2], handles[3]));
			GL.End();
			#endregion
 
			SwapBuffers();
		}
 
		Vector3 CalculateBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3)
		{
			float u = 1 - t;
			float tt = t * t;
			float uu = u * u;
			float uuu = uu * u;
			float ttt = tt * t;
 
			Vector3 p = uuu * p0;
			p += 3 * uu * t * p1;
			p += 3 * u * tt * p2;
			p += ttt * p3;
 
			return p;
		}
 
		[STAThread]
		public static void Main()
		{
			using (Program p = new Program())
			{
				p.Run(80f);
			}
		}
	}
}

For any other comments or remarks or contributions write below.

Images