Converting Triangles into Scan Lines

The JavaScript code below converts each of several different triangles into a series of “scan lines” (that, is, horizontal lines of pixels on a display), and then displays those scan lines (or actually, only every third one, so that you can see them).

To see the program in action, copy it into an .html file and open that file in a web browser that runs JavaScript.

TrianglesWithScanLines

<html>
<body>
<script type='text/javascript'>

// main

function main()
{
	var viewSize = new Coords(300, 200);

	var trianglesToDraw = 
	[
		new Triangle
		([
			new Coords(20, 10),
			new Coords(30, 50),
			new Coords(50, 20)
		]),

		new Triangle
		([
			new Coords(90, 30),
			new Coords(90, 70),
			new Coords(60, 50)
		]),

		new Triangle
		([
			new Coords(110, 60),
			new Coords(110, 100),
			new Coords(140, 80)
		]),

		new Triangle
		([
			new Coords(140, 100),
			new Coords(190, 100),
			new Coords(165, 140)
		]),

		new Triangle
		([
			new Coords(185, 160),
			new Coords(235, 160),
			new Coords(210, 120)
		]),

	];

	Globals.Instance.initialize
	(
		new DisplayHelper(viewSize),
		trianglesToDraw
	);
}

// classes

function Bounds(min, max)
{
	this.min = min;
	this.max = max;
}
{
	Bounds.calculateFromPositions = function(positions)
	{
		var infinity = 1000000; // hack
	
		var minSoFar = new Coords(infinity, infinity);
		var maxSoFar = new Coords(0, 0);
	
		for (var i = 0; i < positions.length; i++)
		{
			var position = positions[i];
			
			if (position.x < minSoFar.x)
			{
				minSoFar.x = position.x;
			}
			if (position.y < minSoFar.y)
			{
				minSoFar.y = position.y;
			}
			if (position.x > maxSoFar.x)
			{
				maxSoFar.x = position.x;
			}
			if (position.y > maxSoFar.y)
			{
				maxSoFar.y = position.y;
			}
		}
	
		var returnValue = new Bounds(minSoFar, maxSoFar);

		return returnValue;
	}

}

function Coords(x, y)
{
	this.x = x;
	this.y = y;
}
{
	Coords.prototype.clone = function()
	{
		return new Coords(this.x, this.y);
	}

	Coords.prototype.divideScalar = function(scalar)
	{
		this.x /= scalar;
		this.y /= scalar;

		return this;
	}

	Coords.prototype.magnitude = function()
	{
		return Math.sqrt(this.x * this.x + this.y * this.y);
	}
	
	Coords.prototype.subtract = function(other)
	{
		this.x -= other.x;
		this.y -= other.y;

		return this;
	}
}

function DisplayHelper(viewSize)
{
	this.viewSize = viewSize;
}
{
	DisplayHelper.prototype.initialize = function()
	{
		var canvas = document.createElement("canvas");
		canvas.width = this.viewSize.x;
		canvas.height = this.viewSize.y;
		this.canvas = canvas;

		this.graphics = this.canvas.getContext("2d");

		document.body.appendChild(this.canvas);
	}	

	DisplayHelper.prototype.clear = function()
	{
		this.graphics.fillStyle = "Black";
		this.graphics.fillRect
		(
			0, 0,
			this.viewSize.x, this.viewSize.y
		);
	}

	DisplayHelper.prototype.drawLine = function(startPos, endPos)
	{
		this.graphics.strokeStyle = "Green";
		this.graphics.beginPath();
		this.graphics.moveTo(startPos.x, startPos.y);
		this.graphics.lineTo(endPos.x, endPos.y);
		this.graphics.stroke();
	}
}

function Edge(vertexPositions)
{
	this.vertexPositions = vertexPositions;
	this.displacementFromVertex0To1 = this.vertexPositions[1].clone().subtract
	(
		this.vertexPositions[0]
	);
	this.length = this.displacementFromVertex0To1.magnitude();
	this.directionFromVertex0To1 = this.displacementFromVertex0To1.clone().divideScalar
	(
		this.length
	);
	this.bounds = Bounds.calculateFromPositions(this.vertexPositions);
}
{
	Edge.buildManyFromVertexPositions = function(vertexPositions)
	{
		var returnValues = [];

		var numberOfVertexPositions = vertexPositions.length;
		for (var i = 0; i < numberOfVertexPositions; i++)
		{
			var vertexPosition = vertexPositions[i];

			var iNext = i + 1;
			if (iNext >= numberOfVertexPositions)
			{
				iNext = 0;
			}

			var vertexPositionNext = vertexPositions[iNext];

			var edge = new Edge([vertexPosition, vertexPositionNext]);

			returnValues.push(edge);
		}

		return returnValues;
	}
}

function Globals()
{}
{
	Globals.Instance = new Globals();

	Globals.Instance.initialize = function(displayHelper, trianglesToDraw)
	{
		this.displayHelper = displayHelper;
		this.displayHelper.initialize();

		this.displayHelper.clear();
		
		for (var i = 0; i < trianglesToDraw.length; i++)
		{
			var triangleToDraw = trianglesToDraw[i];

			triangleToDraw.drawAsScanLines();
		}
	}
}

function Triangle(vertexPositions)
{
	this.vertexPositions = vertexPositions;	
	this.edges = Edge.buildManyFromVertexPositions(this.vertexPositions);

	this.bounds = Bounds.calculateFromPositions(this.vertexPositions);
}
{
	Triangle.prototype.calculateEdgesForScanLines = function()
	{	
		var returnEdges = [];

		var bounds = this.bounds;

		for (var y = bounds.min.y; y < bounds.max.y; y++)
		{	
			var xMinSoFar = bounds.max.x;
			var xMaxSoFar = bounds.min.x;

			for (var i = 0; i < this.edges.length; i++)
			{
				var edge = this.edges[i];
				var edgeBounds = edge.bounds;

				if 
				(
					y >= edgeBounds.min.y 
					&& y <= edgeBounds.max.y
				)
				{

					var edgeDirection = edge.directionFromVertex0To1;

					if (edgeDirection.y != 0)
					{
						var edgeVertex0 = edge.vertexPositions[0];
						var t = (y - edgeVertex0.y) / edgeDirection.y;

						if (t >= 0 && t <= edge.length)
						{
							var x = edgeVertex0.x + edgeDirection.x * t;
	
							if (x < xMinSoFar)
							{
								xMinSoFar = x;
							}
							if (x > xMaxSoFar)
							{
								xMaxSoFar = x;
							}
						}
					}
				}
			}

			var edge = new Edge
			(
				[
					new Coords(xMinSoFar, y),
					new Coords(xMaxSoFar, y)
				]
			);

			returnEdges.push(edge);
		}

		return returnEdges;
	}

	Triangle.prototype.drawAsScanLines = function()
	{
		var displayHelper = Globals.Instance.displayHelper;

		// draw the perimeter first

		for (var e = 0; e < this.edges.length; e++)
		{
			var edge = this.edges[e];
			displayHelper.drawLine
			(
				edge.vertexPositions[0],
				edge.vertexPositions[1]
			)
		}

		var edgesForScanLinesForTriangle = this.calculateEdgesForScanLines();

		// Only draw every third line
		// to better illustrate the results.
		var yStep = 3;

		for (var y = 0; y < edgesForScanLinesForTriangle.length; y += yStep)
		{
			var edgeForScanLine = edgesForScanLinesForTriangle[y];

			displayHelper.drawLine
			(
				edgeForScanLine.vertexPositions[0],
				edgeForScanLine.vertexPositions[1]
			);
		}

	}
}

// run

main();

</script>
</body>
</html>
Advertisements
This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s