Rendering to Texture with WebGL

The JavaScript program below, when run, will display a simple three-dimensional scene rendered with WebGL. It is nearly identical to a program from a previous post, except that this version demonstrates how to render a scene to a texture, rather than directly to the screen. This texture can then be used to texture another mesh within the scene.

This technique of “rendering to texture” is frequently used to model some advanced effects in a 3D scene, such as representing television screens or mirrors. It is also used as the first step in “shadow mapping”, which is a means of adding shadows to a scene by “rendering” it from the point of view of each light source, into a special texture called a “depth map”, and then using that depth map to determine whether a particular pixel in the camera’s view is in shadow or not. This code does not attempt to perform shadow mapping, though it is intended as a first step in that direction.

To see the code in action, copy it into an .html file and open that file in a web browser that runs JavaScript. Use the W, A, S, D, Z, C, E and R keys to move the camera around, and press the Enter key to create a snapshot of the current view, which will then be added as another object in the scene.

UPDATE 2017/02/10 – Adapting this code to do shadow mapping has proven at least as difficult as I imagined. In the face of this continuing difficulty, I have refactored this code somewhat to make debugging easier. Notably, I have added an option to also render the scene as a simple wireframe without using WebGL for comparison purposes, added some further abstraction around the WebGL shader functionality, and simplified the code that builds the camera’s perspective matrix for WebGL.

webglscenerenderedtotexture


<html>
<body>

<script type="text/javascript">

// main

function WebGLTest()
{
	this.main = function()
	{
		var imageTextGL = ImageHelper.buildImageFromStrings
		(
			"ImageTextGL",
			8, // scaleMultiplier
			[
				"RRRRRRRRRRRRRRRR",
				"RRcccccRcRRRRRcR",
				"RRcRRRRRcRRRRRcR",
				"RRcRRRRRcRRRRRcR",
				"RRcRcccRcRRRRRcR",
				"RRcRRRcRcRRRRRRR",
				"RRcccccRcccccRcR",
				"RRRRRRRRRRRRRRRR",
			]
		);

		Globals.Instance.mediaHelper.loadImages
		(
			[ imageTextGL ],
			this.main2
		);
	}

	this.main2 = function(event)
	{
		var mediaHelper = Globals.Instance.mediaHelper;

		var displaySize = new Coords(320, 240, 2000);

		var scene = new DemoData().scene(mediaHelper, displaySize);

		var displays = 
		[
			//new Display2D(displaySize),
			new DisplayWebGL(displaySize),
		];

		Globals.Instance.initialize
		(
			displays,
			scene
		);
	}
}

// extensions

function ArrayExtensions()
{
	// extension class
}
{
	Array.prototype.addLookups = function(keyName)
	{
		for (var i = 0; i < this.length; i++)
		{
			var element = this[i];
			var key = element[keyName];
			this[key] = element;
		}

		return this;
	}

	Array.prototype.append = function(other)
	{
		for (var i = 0; i < other.length; i++)
		{
			var element = other[i];
			this.push(element);
		}

		return this;
	}

	// cloneable

	Array.prototype.clone = function()
	{
		var elementsCloned = [];

		for (var i = 0; i < this.length; i++)
		{
			var element = this[i];
			var elementCloned = element.clone();
			elementsCloned.push(elementCloned);
		}

		return elementsCloned;
	}

	Array.prototype.overwriteWith = function(other)
	{
		for (var i = 0; i < this.length; i++)
		{
			var elementThis = this[i];
			var elementOther = other[i];
			elementThis.overwriteWith(elementOther);
		}

		return this;
	}
}

// classes

function Body(name, defn, pos, orientation)
{
	this.name = name;
	this.defn = defn;
	this.pos = pos;
	this.orientation = orientation;
}
{
	Body.prototype.meshTransformed = function()
	{
		if (this._meshTransformed == null)
		{
			this._meshTransformed = this.defn.mesh.clone();

			this.transformLocate = new TransformMultiple
			([
				new TransformOrient(this.orientation),
				new TransformTranslate(this.pos),
			])
		}
		else
		{
			this._meshTransformed.overwriteWith(this.defn.mesh);
		}

		Transform.transformApplyToCoordsMany
		(
			this.transformLocate,
			this._meshTransformed.vertexPositions
		);

		return this._meshTransformed;
	}
	
	// WebGL

	Body.prototype.drawToWebGLContext = function(webGLContext, scene)
	{
		this.defn.mesh.drawToWebGLContext(webGLContext, scene);
	}
}

function BodyDefn(name, mesh)
{
	this.name = name;
	this.mesh = mesh;
}

function Camera(viewSize, focalLength, pos, orientation)
{
	this.viewSize = viewSize;
	this.focalLength = focalLength;
	this.pos = pos;
	this.orientation = orientation;

	this.viewSizeHalf = this.viewSize.clone().divideScalar(2);
}
{
	Camera.prototype.toString = function()
	{
		var returnValue = "<Camera "
			+ "pos='" + this.pos.toString() + "' "
			+ ">" 
			+ this.orientation.toString()
			+ "</Camera>";

		return returnValue;
	}
}

function Color(name, codeChar, systemColor, componentsRGBA)
{
	this.name = name;
	this.codeChar = codeChar;
	this.systemColor = systemColor;
	this.componentsRGBA = componentsRGBA;
}
{
	// constants

	Color.NumberOfComponentsRGBA = 4;

	// instances

	function Color_Instances()
	{
		this.Transparent = new Color("Transparent", ".", "rgba(0, 0, 0, 0)", [0, 0, 0, 0]);

		this.Black 	= new Color("Black",	"k", "Black", [0, 0, 0, 1]);
		this.Blue 	= new Color("Blue", 	"b", "Blue", [0, 0, 1, 1]);
		this.Cyan 	= new Color("Cyan", 	"c", "Cyan", [0, 1, 1, 1]);
		this.Gray 	= new Color("Gray", 	"a", "Gray", [.5, .5, .5, 1]);
		this.Green 	= new Color("Green", 	"g", "Green", [0, 1, 0, 1]);
		this.GreenDark 	= new Color("GreenDark", "G", "#008000", [0, .5, 0, 1]);
		this.Orange 	= new Color("Orange", 	"o", "Orange", [1, .5, 0, 1]);
		this.Red 	= new Color("Red", 	"r", "Red", [1, 0, 0, 1]);
		this.RedDark 	= new Color("RedDark", 	"R", "#800000", [.5, 0, 0, 1]);
		this.Violet 	= new Color("Violet", 	"v", "Violet", [1, 0, 1, 1]);
		this.White 	= new Color("White", 	"w", "White", [1, 1, 1, 1]);
		this.Yellow 	= new Color("Yellow", 	"y", "Yellow", [1, 1, 0, 1]);

		this._All = 
		[
			this.Transparent,

			this.Blue,
			this.Black,
			this.Cyan,
			this.Gray,
			this.Green,
			this.GreenDark,
			this.Orange,
			this.Red,
			this.RedDark,
			this.Violet,
			this.White,
			this.Yellow,

		].addLookups("codeChar");
	}

	Color.Instances = new Color_Instances();

}

function Constants()
{}
{
	Constants.DegreesPerCircle = 360;
	Constants.RadiansPerCircle = 2 * Math.PI;
	Constants.RadiansPerDegree = 
		Constants.RadiansPerCircle
		/ Constants.DegreesPerCircle;
}

function Coords(x, y, z)
{
	this.x = x;
	this.y = y;
	this.z = z;
}
{
	// constants

	Coords.NumberOfDimensions = 3;

	// instances

	function Coords_Instances()
	{
		this.OnesNegative = new Coords(-1, -1, -1);
		this.Zeroes = new Coords(0, 0, 0);
	}

	Coords.Instances = new Coords_Instances();

	// instance methods

	Coords.prototype.add = function(other)
	{
		this.x += other.x;
		this.y += other.y;
		this.z += other.z;

		return this;
	}

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

	Coords.prototype.crossProduct = function(other)
	{
		return this.overwriteWithXYZ
		(
			this.y * other.z - this.z * other.y,
			this.z * other.x - this.x * other.z,
			this.x * other.y - this.y * other.x
		);

	}

	Coords.prototype.dimensionValues = function()
	{
		return [ this.x, this.y, this.z ];
	}

	Coords.prototype.divide = function(other)
	{
		this.x /= other.x;
		this.y /= other.y;
		this.z /= other.z;

		return this;
	}

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

		return this;
	}

	Coords.prototype.dotProduct = function(other)
	{
		var returnValue =
			this.x * other.x
			+ this.y * other.y
			+ this.z * other.z;

		return returnValue;
	}

	Coords.prototype.magnitude = function()
	{
		var returnValue = Math.sqrt
		(
			this.x * this.x
			+ this.y * this.y
			+ this.z * this.z
		);

		return returnValue;
	}

	Coords.prototype.multiply = function(other)
	{
		this.x *= other.x;
		this.y *= other.y;
		this.z *= other.z;

		return this;
	}

	Coords.prototype.multiplyScalar = function(scalar)
	{
		this.x *= scalar;
		this.y *= scalar;
		this.z *= scalar;

		return this;
	}

	Coords.prototype.normalize = function()
	{
		return this.divideScalar(this.magnitude());
	}

	Coords.prototype.overwriteWith = function(other)
	{
		this.x = other.x;
		this.y = other.y;
		this.z = other.z;

		return this;
	}

	Coords.prototype.overwriteWithXYZ = function(x, y, z)
	{
		this.x = x;
		this.y = y;
		this.z = z;

		return this;
	}

	Coords.prototype.subtract = function(other)
	{
		this.x -= other.x;
		this.y -= other.y;
		this.z -= other.z;

		return this;
	}

	Coords.prototype.toString = function()
	{
		var returnValue = "(" + this.x + "," + this.y + "," + this.z + ")";

		return returnValue;
	}

	Coords.prototype.toWebGLArray = function()
	{
		var returnValues = new Float32Array(this.dimensionValues());

		return returnValues;
	}
}

function Display2D(size)
{
	this.size = size;

	this.colorFore = "LightGray";
	this.colorBack = "White";

	// helper variables

	this.drawPositions = 
	[
		new Coords(), new Coords(), new Coords(), new Coords()
	];
}
{
	Display2D.prototype.initialize = function()
	{
		var canvas = document.createElement("canvas");
		canvas.width = this.size.x;
		canvas.height = this.size.y;
		document.body.appendChild(canvas);

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

	Display2D.prototype.sceneInitialize = function(scene)
	{
		// do nothing
	}

	// drawing

	Display2D.prototype.clear = function()
	{
		this.drawRectangle
		(
			Coords.Instances.Zeroes, 
			this.size,
			this.colorBack, // colorFill
			this.colorFore
		)
	}

	Display2D.prototype.drawFace = function(camera, mesh, face)
	{
		var drawPositions = this.drawPositions;
		var cameraPos = camera.pos;
		var cameraOrientation = camera.orientation;
		var cameraFocalLength = camera.focalLength;

		var vertexIndices = face.vertexIndices;
		for (var i = 0; i < vertexIndices.length; i++)
		{
			var vertexIndex = vertexIndices[i];
			var vertex = mesh.vertexPositions[vertexIndex];

			var drawPos = drawPositions[i];

			drawPos.overwriteWith
			(
				vertex
			).subtract
			(
				cameraPos
			);

			cameraOrientation.projectCoords
			(
				drawPos
			);

			if (drawPos.z <= 0)
			{
				return; // hack
			}

			drawPos.multiplyScalar
			(
				cameraFocalLength / drawPos.z
			).add
			(
				camera.viewSizeHalf
			);

			var debug = "debug";
		}

		this.drawPolygon(drawPositions, null, this.colorFore);
	}

	Display2D.prototype.drawMesh = function(camera, mesh)
	{
		var faces = mesh.faces;
		for (var i = 0; i < faces.length; i++)
		{
			var face = faces[i];
			this.drawFace(camera, mesh, face);
		}
	}

	Display2D.prototype.drawPolygon = function(vertices, colorFill, colorBorder)
	{
		this.graphics.beginPath();

		var vertex = vertices[0];
		this.graphics.moveTo(vertex.x, vertex.y);

		for (var i = 1; i < vertices.length; i++)
		{
			vertex = vertices[i];
			this.graphics.lineTo(vertex.x, vertex.y);
		}
	
		this.graphics.closePath();

		if (colorFill != null)
		{
			this.graphics.fillStyle = colorFill;
			this.graphics.fill();
		}

		if (colorBorder != null)
		{
			this.graphics.strokeStyle = colorFill;
			this.graphics.stroke();
		}
	}

	Display2D.prototype.drawRectangle = function(pos, size, colorFill, colorBorder)
	{
		if (colorFill != null)
		{
			this.graphics.fillStyle = colorFill;
			this.graphics.fillRect
			(
				pos.x, pos.y,
				size.x, size.y
			);
		}

		if (colorBorder != null)
		{
			this.graphics.strokeStyle = colorBorder;
			this.graphics.strokeRect
			(
				pos.x, pos.y,
				size.x, size.y
			);
		}
	}

	Display2D.prototype.drawScene = function(scene)
	{
		this.clear();

		var camera = scene.camera;
		var bodies = scene.bodies;

		for (var i = 0; i < bodies.length; i++)
		{
			var body = bodies[i];
			var mesh = body.meshTransformed();
			this.drawMesh(camera, mesh);
		}
	}

}

function DisplayWebGL(size)
{
	this.size = size;
}
{
	DisplayWebGL.prototype.initialize = function()
	{
		var canvas = document.createElement("canvas");
		canvas.width = this.size.x;
		canvas.height = this.size.y;
		document.body.appendChild(canvas);

		this.webGLContext = new WebGLContext(canvas);
	}

	DisplayWebGL.prototype.sceneInitialize = function(scene)
	{
		var textures = scene.textures;

		for (var i = 0; i < textures.length; i++)
		{
			var texture = textures[i];
			this.textureInitialize(texture);
		}
	}

	DisplayWebGL.prototype.textureInitialize = function(texture)
	{
		var gl = this.webGLContext.gl;

		var systemTexture = gl.createTexture();
		texture.systemTexture = systemTexture;

		gl.bindTexture(gl.TEXTURE_2D, systemTexture);
		//gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
		gl.texImage2D
		(
			gl.TEXTURE_2D, 
			0, 
			gl.RGBA, 
			gl.RGBA, 
			gl.UNSIGNED_BYTE, 
			texture.image.systemImage
		);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
		gl.bindTexture(gl.TEXTURE_2D, null);
	}

	// drawing

	DisplayWebGL.prototype.drawScene = function(scene)
	{
		scene.drawToWebGLContext
		(
			this.webGLContext
		);
	}
}

function Face(vertexIndices, material, textureUVs, vertexNormals)
{
	this.vertexIndices = vertexIndices;
	this.material = material;
	this.textureUVs = textureUVs;
	this.vertexNormals = vertexNormals;
}
{
	// constants

	Face.VertexIndexIndicesForQuad = [ [ 0, 1, 2 ], [ 0, 2, 3 ] ];
	Face.VertexIndexIndicesForTriangle = [ [ 0, 1, 2 ] ];

	// methods

	Face.prototype.plane = function(mesh)
	{
		var returnValue = new Plane(this.vertices(mesh));
		return returnValue;
	}

	Face.prototype.vertices = function(mesh)
	{
		var returnValues = [];

		for (var i = 0; i < this.vertexIndices.length; i++)
		{
			var vertexIndex = this.vertexIndices[i];
			var vertexPosition = mesh.vertexPositions[vertexIndex];
			returnValues.push(vertexPosition);
		}

		return returnValues;
	}

	// cloneable

	Face.prototype.clone = function()
	{
		return new Face
		(
			this.vertexIndices, // hack
			this.material, // hack
			(this.textureUVS == null ? null : this.textureUVs.clone()),
			(this.vertexNormals == null ? null : this.vertexNormals.clone())
		);
	}

	// WebGL

	Face.prototype.drawToWebGLContext = function
	(
		webGLContext, 
		scene, 
		mesh,
		vertexPositionsAsFloatArray,
		vertexColorsAsFloatArray,
		vertexNormalsAsFloatArray,
		vertexTextureUVsAsFloatArray,		
		numberOfTrianglesSoFarWrapped
	)
	{
		var face = this;
		var facePlane = face.plane(mesh);
		var faceNormal = facePlane.normal;
		var faceMaterial = face.material;
		var faceVertexNormals = face.vertexNormals;
		var faceColor = (faceMaterial == null ? Color.Instances.Violet : faceMaterial.color);

		var numberOfVerticesInFace = face.vertexIndices.length;

		var vertexIndexIndicesForChildTriangles = 
			(numberOfVerticesInFace == 4) 
			? Face.VertexIndexIndicesForQuad
			: Face.VertexIndexIndicesForTriangle

		for (var t = 0; t < vertexIndexIndicesForChildTriangles.length; t++)
		{
			var vertexIndexIndicesForChildTriangle = 
				vertexIndexIndicesForChildTriangles[t];

			for (var vii = 0; vii < vertexIndexIndicesForChildTriangle.length; vii++)
			{
				var vertexIndexIndex = 
					vertexIndexIndicesForChildTriangle[vii];
				var vertexIndex = face.vertexIndices[vertexIndexIndex];
				var vertexPosition = mesh.vertexPositions[vertexIndex];

				vertexPositionsAsFloatArray.append
				(
					vertexPosition.dimensionValues()
				);

				vertexColorsAsFloatArray.append
				(
					faceColor.componentsRGBA
				);

				var vertexNormal = 
				(
					faceVertexNormals == null 
					? faceNormal
					: faceVertexNormals[vertexIndex]
				);

				vertexNormalsAsFloatArray.append
				(
					vertexNormal.dimensionValues()
				);

				var vertexTextureUV = 
				(
					face.textureUVs == null 
					? Coords.Instances.OnesNegative
					: face.textureUVs[vertexIndexIndex]
				);

				vertexTextureUVsAsFloatArray.append
				(
					[
						vertexTextureUV.x,
						vertexTextureUV.y
					]
				);
			}
		}

		numberOfTrianglesSoFarWrapped.value 
			+= vertexIndexIndicesForChildTriangles.length;
	}
}

function Globals()
{
	this.mediaHelper = new MediaHelper();
}
{
	Globals.Instance = new Globals();

	Globals.prototype.initialize = function(displays, scene)
	{
		this.displays = displays;
		this.scene = scene;
	
		for (var i = 0; i < this.displays.length; i++)
		{
			var display = this.displays[i];
			display.initialize();
			display.sceneInitialize(scene);
		}

		this.inputHelper = new InputHelper();
		this.inputHelper.initialize();

		for (var i = 0; i < this.displays.length; i++)
		{
			var display = this.displays[i];
			this.scene.drawToDisplay(display);
		}
	}
}

function Image(name, systemImage)
{
	this.name = name;
	this.systemImage = systemImage;
	this.filePath = this.systemImage.src;
}
{
}

function ImageHelper()
{}
{
	// static methods

	ImageHelper.buildImageFromStrings = function
	(
		name, 
		scaleMultiplier, 
		stringsForPixels
	)
	{
		var sizeInPixels = new Coords
		(
			stringsForPixels[0].length, stringsForPixels.length
		);

		var canvas = document.createElement("canvas");
		canvas.width = sizeInPixels.x * scaleMultiplier;
		canvas.height = sizeInPixels.y * scaleMultiplier;

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

		var pixelPos = new Coords(0, 0);
		var colorForPixel = Color.Instances.Transparent;

		for (var y = 0; y < sizeInPixels.y; y++)
		{
			var stringForPixelRow = stringsForPixels[y];
			pixelPos.y = y * scaleMultiplier;

			for (var x = 0; x < sizeInPixels.x; x++)
			{
				var charForPixel = stringForPixelRow[x];
				pixelPos.x = x * scaleMultiplier;

				colorForPixel = Color.Instances._All[charForPixel];

				graphics.fillStyle = colorForPixel.systemColor;
				graphics.fillRect
				(
					pixelPos.x, pixelPos.y, 
					scaleMultiplier, scaleMultiplier
				);				
			}
		}

		var imageFromCanvasURL = canvas.toDataURL("image/png");
		var htmlImageFromCanvas = document.createElement("img");

		htmlImageFromCanvas.width = canvas.width;
		htmlImageFromCanvas.height = canvas.height;
		htmlImageFromCanvas.isLoaded = false;
		htmlImageFromCanvas.onload = function(event) 
		{ 
			event.target.isLoaded = true; 
		}
		htmlImageFromCanvas.src = imageFromCanvasURL;

		var returnValue = new Image(name, htmlImageFromCanvas);

		// hack
		// WebGL doesn't support images from DataURLs?
		returnValue.canvas = canvas;

		return returnValue;
	}
}

function InputHelper()
{
	this.tempCoords = new Coords();
}
{
	InputHelper.prototype.initialize = function()
	{
		document.body.onkeydown = this.processKeyDownEvent.bind(this);
	}

	InputHelper.prototype.processKeyDownEvent = function(event)
	{
		var scene = Globals.Instance.scene
		var camera = scene.camera;

		var key = event.key.toLowerCase();

		var distanceToMove = 10;
		var amountToTurn = .05;
		var cameraOrientation = camera.orientation;

		if (key == "a")
		{
			// move left
			camera.pos.subtract
			(
				this.tempCoords.overwriteWith
				(
					cameraOrientation.right
				).multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (key == "d")
		{
			// move right
			camera.pos.add
			(
				this.tempCoords.overwriteWith
				(
					cameraOrientation.right
				).multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (key == "e")
		{
			// roll right
			cameraOrientation.overwriteWithForwardDown
			(
				cameraOrientation.forward,
				cameraOrientation.down.add
				(
					cameraOrientation.right.multiplyScalar
					(
						amountToTurn
					)
				)
			);

		}
		else if (key == "f")
		{
			// fall
			camera.pos.subtract
			(
				this.tempCoords.overwriteWith
				(
					cameraOrientation.down
				).multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (key == "q")
		{
			// roll left
			cameraOrientation.overwriteWithForwardDown
			(
				cameraOrientation.forward,
				cameraOrientation.down.subtract
				(
					cameraOrientation.right.multiplyScalar
					(
						amountToTurn
					)
				)
			);

		}
		else if (key == "r")
		{
			// rise
			camera.pos.add
			(
				this.tempCoords.overwriteWith
				(
					cameraOrientation.down
				).multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (key == "s")
		{
			// move back
			camera.pos.subtract
			(
				this.tempCoords.overwriteWith
				(
					cameraOrientation.forward
				).multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (key == "w")
		{
			// move forward
			camera.pos.add
			(
				this.tempCoords.overwriteWith
				(
					cameraOrientation.forward
				).multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (key == "z")
		{
			// turn left
			cameraOrientation.overwriteWithForwardDown
			(
				cameraOrientation.forward.subtract
				(
					cameraOrientation.right.multiplyScalar
					(
						amountToTurn
					)
				),
				cameraOrientation.down
			);
		}
		else if (key == "c")
		{
			// turn right
			cameraOrientation.overwriteWithForwardDown
			(
				cameraOrientation.forward.add
				(
					cameraOrientation.right.multiplyScalar
					(
						amountToTurn
					)
				),
				cameraOrientation.down
			);
		}
		else if (key == "x")
		{
			// cancel roll
			cameraOrientation.overwriteWithForwardDown
			(
				cameraOrientation.forward,
				new Coords(0, 0, 1)
			);
		}
		else if (key == "enter")
		{
			// take a snapshot
			scene.snapshotCreate(scene);
		}

		var displays = Globals.Instance.displays;
		for (var i = 0; i < displays.length; i++)
		{
			var display = displays[i];
			scene.drawToDisplay(display);
		}
	}
}

function Lighting(ambientIntensity, direction, directionalIntensity)
{
	this.ambientIntensity = ambientIntensity;
	this.direction = direction.clone().normalize();
	this.directionalIntensity = directionalIntensity;
}

function Material(name, color)
{
	this.name = name;
	this.color = color;
}
{	
	function Material_Instances()
	{
		var colors = Color.Instances;

		this.Blue 	= new Material("Blue", 	colors.Blue);
		this.Cyan 	= new Material("Cyan", 	colors.Cyan);
		this.Green 	= new Material("Green", colors.Green);
		this.GreenDark 	= new Material("GreenDark", colors.GreenDark);
		this.Orange 	= new Material("Orange", colors.Orange);
		this.Red 	= new Material("Red", 	colors.Red);
		this.Violet 	= new Material("Violet", colors.Violet);
		this.Yellow 	= new Material("Yellow", colors.Yellow);
		this.White 	= new Material("White", colors.White);
	}

	Material.Instances = new Material_Instances();
}

function Matrix(values)
{
	this.values = values;
}
{
	// static methods

	Matrix.buildZeroes = function()
	{
		var returnValue = new Matrix
		([
			0, 0, 0, 0,
			0, 0, 0, 0,
			0, 0, 0, 0,
			0, 0, 0, 0,
		]);

		return returnValue;
	}

	// instance methods

	Matrix.prototype.clone = function()
	{
		var valuesCloned = [];

		for (var i = 0; i < this.values.length; i++)
		{
			valuesCloned[i] = this.values[i];
		}

		var returnValue = new Matrix(valuesCloned);

		return returnValue;
	}

	Matrix.prototype.divideScalar = function(scalar)
	{
		for (var i = 0; i < this.values.length; i++)
		{
			this.values[i] /= scalar;
		}

		return this;
	}

	Matrix.prototype.multiply = function(other)
	{
		// hack
		// Instantiates a new matrix.

		var valuesMultiplied = [];

		for (var y = 0; y < 4; y++)
		{
			for (var x = 0; x < 4; x++)
			{
				var valueSoFar = 0;

				for (var i = 0; i < 4; i++)
				{
					// This appears backwards,
					// but the other way doesn't work?
					valueSoFar += 
						other.values[y * 4 + i] 
						* this.values[i * 4 + x];
				}

				valuesMultiplied[y * 4 + x] = valueSoFar;
			}
		}

		this.overwriteWithValues(valuesMultiplied);

		return this;
	}

	Matrix.prototype.multiplyScalar = function(scalar)
	{
		for (var i = 0; i < this.values.length; i++)
		{
			this.values[i] *= scalar;
		}

		return this;
	}

	Matrix.prototype.overwriteWith = function(other)
	{
		for (var i = 0; i < this.values.length; i++)
		{
			this.values[i] = other.values[i];
		}

		return this;
	}

	Matrix.prototype.overwriteWithOrientationBody = function(orientation)
	{
		var forward = orientation.forward;
		var right = orientation.right;
		var down = orientation.down;

		this.overwriteWithValues
		([
			right.x, 	down.x, 	forward.x, 	0,
			right.y, 	down.y,		forward.y, 	0,
			right.z, 	down.z, 	forward.z, 	0,
			0, 		0, 		0, 		1,
		]);


		return this;
	}

	Matrix.prototype.overwriteWithOrientationCamera = function(orientation)
	{
		var forward = orientation.forward;
		var right = orientation.right;
		var down = orientation.down;

		this.overwriteWithValues
		([

			right.x, 	right.y, 	right.z, 	0,
			0-down.x, 	0-down.y,	0-down.z, 	0,
			0-forward.x, 	0-forward.y, 	0-forward.z, 	0,
			0, 		0, 		0, 		1,
		]);
	

		return this;
	}

	Matrix.prototype.overwriteWithPerspectiveForCamera = function(camera) 
	{
		var viewSize = camera.viewSize;
		var y = camera.focalLength / (viewSize.y / 2);
		var x = camera.focalLength / (viewSize.x / 2);
		var zNear = .001;
		var zFar = viewSize.z;
		var zRange = zNear - zFar; // Yes, it's negative.

		this.overwriteWithValues
		([
			x, 	0,	0,			0,
			0,	y, 	0, 			0,
			0, 	0,	(zNear+zFar)/zRange,	(2*zFar*zNear)/zRange,
			0,	0,	-1,			0,
		]);

		return this;
	}

	Matrix.prototype.overwriteWithRotate = function(axisToRotateAround, radiansToRotate)
	{
		var x = axisToRotateAround.x;
		var y = axisToRotateAround.y;
		var z = axisToRotateAround.z;

		var cosine = Math.cos(radiansToRotate);
		var sine = Math.sin(radiansToRotate);
		var cosineReversed = 1 - cosine;

		var xSine = x * sine;
		var ySine = y * sine;
		var zSine = z * sine;

		var xCosineReversed = x * cosineReversed;
		var yCosineReversed = y * cosineReversed;
		var zCosineReversed = z * cosineReversed;

		var xyCosineReversed = x * yCosineReversed;
		var xzCosineReversed = x * zCosineReversed;
		var yzCosineReversed = y * zCosineReversed;

		this.overwriteWithValues
		([
			(x * xCosineReversed + cosine), 	(xyCosineReversed + z * sine), 		(xzCosineReversed - ySine), 	0,
			(xyCosineReversed - zSine), 		(y * yCosineReversed + cosine), 	(yzCosineReversed + xSine), 	0,
			(xzCosineReversed + ySine), 		(yzCosineReversed - xSine), 		(z * zCosineReversed + cosine), 0,
			0,					0, 					0, 				1,
		]);

		return this;
	}

	Matrix.prototype.overwriteWithScale = function(scaleFactors)
	{
		this.overwriteWithValues
		([
			scaleFactors.x,	0, 		0, 		0,
			0, 		scaleFactors.y, 0, 		0,
			0, 		0, 		scaleFactors.z, 0,
			0, 		0, 		0, 		1,

		]);

		return this;
	}

	Matrix.prototype.overwriteWithTranslate = function(displacement)
	{
		this.overwriteWithValues
		([
			1, 0, 0, displacement.x,
			0, 1, 0, displacement.y,
			0, 0, 1, displacement.z,
			0, 0, 0, 1,

		]);

		return this;
	}

	Matrix.prototype.overwriteWithValues = function(otherValues)
	{
		for (var i = 0; i < this.values.length; i++)
		{
			this.values[i] = otherValues[i];
		}

		return this;
	}

	Matrix.prototype.toWebGLArray = function()
	{
		var returnValues = [];

		for (var x = 0; x < 4; x++)
		{
			for (var y = 0; y < 4; y++)
			{
				returnValues.push(this.values[(y * 4 + x)]);
			}
		}

		var returnValues = new Float32Array(returnValues);

		return returnValues;
	}
}

function MediaHelper()
{
	this.images = [];
}
{
	MediaHelper.prototype.loadImages = function
	(
		imagesToLoad,
		methodToCallWhenAllImagesLoaded
	)
	{
		for (var i = 0; i < imagesToLoad.length; i++)
		{
			var imageToLoad = imagesToLoad[i];

			this.images.push(imageToLoad);
			this.images[imageToLoad.name] = imageToLoad;
		}

		this.methodToCallWhenAllImagesLoaded = methodToCallWhenAllImagesLoaded;	

		setTimeout
		(
			this.checkWhetherAllImagesAreLoaded, 
			100
		);
	}

	MediaHelper.prototype.checkWhetherAllImagesAreLoaded = function()
	{
		var mediaHelper = Globals.Instance.mediaHelper;

		var numberOfImagesLeftToLoad = 0;

		for (var i = 0; i < mediaHelper.images.length; i++)
		{
			var image = mediaHelper.images[i];
			if (image.systemImage.isLoaded == false)
			{
				numberOfImagesLeftToLoad++;
			}
		}	

		if (numberOfImagesLeftToLoad > 0)
		{
			setTimeout
			(
				mediaHelper.checkWhetherAllImagesAreLoaded, 
				100
			);
		}
		else
		{
			mediaHelper.methodToCallWhenAllImagesLoaded();
		}
	}
}

function Mesh
(
	name, 
	vertexPositions, 
	texture,
	faces
)
{
	this.name = name;
	this.vertexPositions = vertexPositions;
	this.texture = texture;
	this.faces = faces;
}
{
	// constants

	Mesh.VerticesInATriangle = 3;
	Mesh.TextureUVsDefault = 
	[ 
		new Coords(0, 1), 
		new Coords(1, 1), 
		new Coords(1, 0), 
		new Coords(0, 0) 
	];

	// instance methods

	// cloneable

	Mesh.prototype.clone = function()
	{
		return new Mesh
		(
			this.name + "_Cloned",
			this.vertexPositions.clone(),
			(this.texture == null ? null : this.texture.clone())	,
			this.faces.clone()
		);
	}

	Mesh.prototype.overwriteWith = function(other)
	{
		this.vertexPositions.overwriteWith(other.vertexPositions);
	}

	// WebGL

	Mesh.prototype.drawToWebGLContext = function(webGLContext, scene)
	{
		var gl = webGLContext.gl;

		var shader = webGLContext.shader;

		var vertexPositionsAsFloatArray = [];
		var vertexColorsAsFloatArray = [];
		var vertexNormalsAsFloatArray = [];
		var vertexTextureUVsAsFloatArray = [];

		var numberOfTrianglesSoFarWrapped = new NumberWrapper(0);

		for (var f = 0; f < this.faces.length; f++)
		{
			var face = this.faces[f];
			face.drawToWebGLContext
			(
				webGLContext, 
				scene, 
				this, // mesh
				vertexPositionsAsFloatArray,
				vertexColorsAsFloatArray,
				vertexNormalsAsFloatArray,
				vertexTextureUVsAsFloatArray,
				numberOfTrianglesSoFarWrapped
			);
		}

		var colorBuffer = gl.createBuffer();
		gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
		gl.bufferData
		(
			gl.ARRAY_BUFFER, 
			new Float32Array(vertexColorsAsFloatArray), 
			gl.STATIC_DRAW
		);
		gl.vertexAttribPointer
		(
			shader.inputs["aVertexColor"].location, 
			Color.NumberOfComponentsRGBA, 
			gl.FLOAT, 
			false, 
			0, 
			0
		);

		var normalBuffer = gl.createBuffer();
		gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
		gl.bufferData
		(
			gl.ARRAY_BUFFER, 
			new Float32Array(vertexNormalsAsFloatArray), 
			gl.STATIC_DRAW
		);		
		gl.vertexAttribPointer
		(
			shader.inputs["aVertexNormal"].location, 
			Coords.NumberOfDimensions, 
			gl.FLOAT, 
			false, 
			0, 
			0
		);

		var positionBuffer = gl.createBuffer();
		gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
		gl.bufferData
		(
			gl.ARRAY_BUFFER, 
			new Float32Array(vertexPositionsAsFloatArray), 
			gl.STATIC_DRAW
		);
		gl.vertexAttribPointer
		(
			shader.inputs["aVertexPosition"].location,
			Coords.NumberOfDimensions, 
			gl.FLOAT, 
			false, 
			0, 
			0
		);

		// hack - Prevents some texture-related warnings and errors.
		var textureToBind = (this.texture == null ? scene.textures[0] : this.texture);
		gl.activeTexture(gl.TEXTURE0);
		gl.bindTexture(gl.TEXTURE_2D, textureToBind.systemTexture);

		gl.uniform1i(shader.inputs["uSampler"], 0);

		var textureBuffer = gl.createBuffer();
		gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);
		gl.bufferData
		(
			gl.ARRAY_BUFFER, 
			new Float32Array(vertexTextureUVsAsFloatArray), 
			gl.STATIC_DRAW
		);
		gl.vertexAttribPointer
		(
			shader.inputs["aVertexTextureUV"].location, 
			2, 
			gl.FLOAT, 
			false, 
			0, 
			0
		);

		gl.drawArrays
		(
			gl.TRIANGLES,
			0, 
			numberOfTrianglesSoFarWrapped.value 
				* Mesh.VerticesInATriangle
		);
	}
}

function NumberWrapper(value)
{
	this.value = value;
}

function Orientation(forward, down)
{
	this.forward = new Coords();
	this.down = new Coords();
	this.right = new Coords();

	this.overwriteWithForwardDown(forward, down);
}
{
	// instance methods

	Orientation.prototype.clone = function()
	{
		return new Orientation
		(
			this.forward, 
			this.down
		);
	}

	Orientation.prototype.overwriteWithForwardDown = function(forward, down)
	{
		this.forward.overwriteWith(forward).normalize();
		this.down.overwriteWith(down).normalize();
		this.right.overwriteWith(this.down).crossProduct(this.forward).normalize();
	}

	Orientation.prototype.projectCoords = function(coordsToProject)
	{
		coordsToProject.overwriteWithXYZ
		(
			coordsToProject.dotProduct(this.right),
			coordsToProject.dotProduct(this.down),
			coordsToProject.dotProduct(this.forward)
		);
	}

	Orientation.prototype.toString = function()
	{
		var returnValue = "<Orientation "
			+ "forward='" + this.forward.toString() + "' "
			+ "right='" + this.right.toString() + "' "
			+ "down='" + this.down.toString() + "' "
			+ " />";

		return returnValue;			
	}
}

function Plane(positionsOnPlane)
{
	var pos0 = positionsOnPlane[0];
	var displacementFromPos0To1 = Plane.TempCoords0.overwriteWith
	(
		positionsOnPlane[1]
	).subtract
	(
		pos0
	);
	var displacementFromPos0To2 = Plane.TempCoords1.overwriteWith
	(
		positionsOnPlane[2]
	).subtract
	(
		pos0
	);
	var normal = displacementFromPos0To1.clone().crossProduct
	(
		displacementFromPos0To2
	).normalize();

	this.normal = normal;
	this.distanceFromOrigin = this.normal.dotProduct(pos0);
}
{
	// helper variables

	Plane.TempCoords0 = new Coords();
	Plane.TempCoords1 = new Coords();
}

function Scene(name, lighting, camera, textures, bodies)
{
	this.name = name;
	this.lighting = lighting;
	this.camera = camera;
	this.textures = textures;
	this.bodies = bodies;

	// helper variables

	this.matrixBody = Matrix.buildZeroes();
	this.matrixCamera = Matrix.buildZeroes();
	this.matrixOrient = Matrix.buildZeroes();
	this.matrixPerspective = Matrix.buildZeroes();
	this.matrixTranslate = Matrix.buildZeroes();
	this.tempCoords = new Coords();
	this.tempMatrix0 = Matrix.buildZeroes();
	this.tempMatrix1 = Matrix.buildZeroes();
}
{
	Scene.prototype.snapshotCreate = function(scene)
	{
		var textureSize = new Coords(512, 512);

		var webGLContext = Globals.Instance.displays[0].webGLContext;
		var gl = webGLContext.gl;

		var framebuffer = gl.createFramebuffer();
		gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);

		var glTexture = gl.createTexture();
		gl.bindTexture(gl.TEXTURE_2D, glTexture);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
		//gl.generateMipmap(gl.TEXTURE_2D);
		gl.texImage2D
		(
			gl.TEXTURE_2D, 
			0, 
			gl.RGBA, 
			textureSize.x, textureSize.y, 
			0, 
			gl.RGBA, 
			gl.UNSIGNED_BYTE, 
			null
		);

		var renderbuffer = gl.createRenderbuffer();
		gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
		gl.renderbufferStorage
		(
			gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, textureSize.x, textureSize.y
		);

		gl.framebufferTexture2D
		(
			gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, glTexture, 0
		);
		gl.framebufferRenderbuffer
		(
			gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer
		);

		this.drawToWebGLContext(webGLContext);

		gl.bindTexture(gl.TEXTURE_2D, null);
		gl.bindRenderbuffer(gl.RENDERBUFFER, null);
		gl.bindFramebuffer(gl.FRAMEBUFFER, null);

		var texture = new Texture("Snapshot", null);
		texture.systemTexture = glTexture;

		var camera = this.camera;
		var meshSize = camera.viewSize.clone().divideScalar(16);
		var meshSizeHalf = meshSize.clone().divideScalar(2);
		var cameraViewOverTextureSize = camera.viewSize.clone().divide(textureSize);

		var meshForSnapshot = new Mesh
		(
			"Snapshot", 
			// vertexPositions
			[
				new Coords(-1, -1, 0).multiply(meshSizeHalf),
				new Coords(1, -1, 0).multiply(meshSizeHalf),
				new Coords(1, 1, 0).multiply(meshSizeHalf),
				new Coords(-1, 1, 0).multiply(meshSizeHalf),
			],
			texture,
			// faces
			[
				new Face
				(
					[0, 1, 2, 3],
					null, // material
					[
						new Coords(0, 1).multiply(cameraViewOverTextureSize),
						new Coords(1, 1).multiply(cameraViewOverTextureSize),
						new Coords(1, 0).multiply(cameraViewOverTextureSize),
						new Coords(0, 0).multiply(cameraViewOverTextureSize),
					]
				),
			]
		);

		var cameraOrientation = camera.orientation;

		var bodyForSnapshot = new Body
		(
			"Snapshot", 
			new BodyDefn("Snapshot", meshForSnapshot),
			camera.pos.clone().add
			(
				cameraOrientation.forward.clone().multiplyScalar
				(
					camera.focalLength / 4
				)
			), 
			cameraOrientation.clone()
		);

		scene.bodies.push(bodyForSnapshot);		
	}

	// draw

	Scene.prototype.drawToDisplay = function(display)
	{
		display.drawScene(this);
	}

	Scene.prototype.drawToWebGLContext = function(webGLContext)
	{
		var gl = webGLContext.gl;
		var shader = webGLContext.shader;

		gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

		var camera = this.camera;

		var cameraMatrix = this.matrixCamera.overwriteWithTranslate
		(
			this.tempCoords.overwriteWith(camera.pos).multiplyScalar(-1)
		).multiply
		(
			this.matrixOrient.overwriteWithOrientationCamera
			(
				camera.orientation
			)
		).multiply
		(
			this.matrixPerspective.overwriteWithPerspectiveForCamera
			(
				camera
			)
		)

		gl.uniform1f
		(
			shader.inputs["uLightAmbientIntensity"].location,
			this.lighting.ambientIntensity
		);

		gl.uniform3fv
		(
			shader.inputs["uLightDirection"].location, 
			this.lighting.direction.toWebGLArray()
		);

		gl.uniform1f
		(
			shader.inputs["uLightDirectionalIntensity"].location,
			this.lighting.directionalIntensity
		);

		gl.uniformMatrix4fv
		(
			shader.inputs["uCameraMatrix"].location, 
			false, // transpose
			cameraMatrix.toWebGLArray()
		);

		for (var b = 0; b < this.bodies.length; b++)
		{
			var body = this.bodies[b];

			var normalMatrix = this.matrixOrient.overwriteWithOrientationBody
			(
				body.orientation
			);

			var bodyMatrix = this.matrixBody.overwriteWith
			(
				normalMatrix
			).multiply
			(
				this.matrixTranslate.overwriteWithTranslate
				(
					body.pos
				)
			);

			gl.uniformMatrix4fv
			(
				shader.inputs["uBodyMatrix"].location, 
				false, // transpose
				bodyMatrix.toWebGLArray()
			);

			gl.uniformMatrix4fv
			(
				shader.inputs["uNormalMatrix"].location, 
				false, // transpose
				normalMatrix.multiplyScalar(-1).toWebGLArray()
			);

			body.drawToWebGLContext(webGLContext, this);
		}
	}
}

function Texture(name, image)
{
	this.name = name;
	this.image = image;
}
{
	Texture.prototype.clone = function()
	{
		return this; // hack
	}
}

function Transform()
{
	// static class
}
{
	Transform.transformApplyToCoordsMany = function(transform, coordsMany)
	{
		for (var i = 0; i < coordsMany.length; i++)
		{
			var coordsToTransform = coordsMany[i];
			transform.applyToCoords(coordsToTransform);
		}
	}
}

function TransformMultiple(children)
{
	this.children = children;
}
{
	TransformMultiple.prototype.applyToCoords = function(coordsToTransform)
	{
		for (var i = 0; i < this.children.length; i++)
		{
			var child = this.children[i];
			child.applyToCoords(coordsToTransform);
		}

		return coordsToTransform;
	}
}

function TransformOrient(orientation)
{
	this.orientation = orientation;
}
{
	TransformOrient.prototype.applyToCoords = function(coordsToTransform)
	{
		return coordsToTransform.overwriteWith
		(
			this.orientation.forward.clone().multiplyScalar
			(	
				coordsToTransform.z
			).add
			(
				this.orientation.right.clone().multiplyScalar
				(
					coordsToTransform.x
				)
			).add
			(
				this.orientation.down.clone().multiplyScalar
				(
					coordsToTransform.y
				)
			)
		);
	}
}

function TransformTranslate(offset)
{
	this.offset = offset;
}
{
	TransformTranslate.prototype.applyToCoords = function(coordsToTransform)
	{
		return coordsToTransform.add(this.offset);
	}
}

function WebGLContext(canvas)
{
	var vertexShaderInputs = 
	[
		new WebGLShaderInput("Attribute", "aVertexColor"),
		new WebGLShaderInput("Attribute", "aVertexNormal"),
		new WebGLShaderInput("Attribute", "aVertexPosition"),
		new WebGLShaderInput("Attribute", "aVertexTextureUV"),

		new WebGLShaderInput("Uniform", "uBodyMatrix"),
		new WebGLShaderInput("Uniform", "uCameraMatrix"),
		new WebGLShaderInput("Uniform", "uLightAmbientIntensity"),
		new WebGLShaderInput("Uniform", "uLightDirection"),
		new WebGLShaderInput("Uniform", "uLightDirectionalIntensity"),
		new WebGLShaderInput("Uniform", "uNormalMatrix"),
	];

	var vertexShaderCode = document.getElementById
	(
		"divShaderCodeVertex"
	).childNodes[0].nodeValue;

	var fragmentShaderCode = document.getElementById
	(
		"divShaderCodeFragment"
	).childNodes[0].nodeValue;

	this.shader = new WebGLShader
	(
		vertexShaderInputs,
		vertexShaderCode,
		fragmentShaderCode
	);
	
	this.initialize(canvas);
}
{
	WebGLContext.prototype.initialize = function(canvas)
	{
		var gl = canvas.getContext("webgl");
		this.gl = gl;

		gl.viewportWidth = canvas.width;
		gl.viewportHeight = canvas.height;

		var colorBack = Color.Instances.Cyan;
		var colorBackComponentsRGBA = colorBack.componentsRGBA;
		gl.clearColor
		(
			colorBackComponentsRGBA[0], 
			colorBackComponentsRGBA[1], 
			colorBackComponentsRGBA[2], 
			colorBackComponentsRGBA[3]
		);

		gl.enable(gl.DEPTH_TEST);

		this.shader.initialize(gl);
	}
}

function WebGLShader(inputs, vertexShaderCode, fragmentShaderCode)
{
	this.inputs = inputs;
	this.vertexShaderCode = vertexShaderCode;
	this.fragmentShaderCode = fragmentShaderCode;

	this.inputs.addLookups("name");
}
{
	WebGLShader.prototype.initialize = function(gl)
	{
		var vertexShader = gl.createShader(gl.VERTEX_SHADER);
		gl.shaderSource(vertexShader, this.vertexShaderCode);
		gl.compileShader(vertexShader);

		var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
		gl.shaderSource(fragmentShader, this.fragmentShaderCode);
		gl.compileShader(fragmentShader);

		var systemShader = gl.createProgram();
		gl.attachShader(systemShader, vertexShader);
		gl.attachShader(systemShader, fragmentShader);
		gl.linkProgram(systemShader);
		gl.useProgram(systemShader);

		this.systemShader = systemShader;

		for (var i = 0; i < this.inputs.length; i++)
		{
			var input = this.inputs[i];
			input.initialize(gl, this.systemShader);
		}
	}
}

function WebGLShaderInput(typeName, name)
{
	this.typeName = typeName;
	this.name = name;
}
{
	WebGLShaderInput.prototype.initialize = function(gl, systemShader)
	{
		var returnValue;

		if (this.typeName == "Attribute")
		{
			returnValue = gl.getAttribLocation
			(
				systemShader, this.name
			);
			gl.enableVertexAttribArray(returnValue);
		}
		else if (this.typeName == "Uniform")
		{
			returnValue = gl.getUniformLocation
			(
				systemShader, this.name
			);
		}
		else
		{
			throw "Unexpected WebGLShaderInput.typeName.";
		}

		this.location = returnValue;
	}
}

// demo

function DemoData()
{
	// do nothing
}
{
	DemoData.prototype.scene = function(mediaHelper, displaySize)
	{
		var imageTextGL = mediaHelper.images["ImageTextGL"];

		var textureTextGL = new Texture
		(
			"TextureTextGL",
			imageTextGL
		);

		var materials = Material.Instances;

		var meshGround = new Mesh
		(
			"Ground",
			// vertexPositions
			[
				new Coords(1000, -1000, 0),
				new Coords(1000, 1000, 0),
				new Coords(-1000, 1000, 0), 
				new Coords(-1000, -1000, 0),
			],
			null, // texture
			// faces
			[
				new Face
				(
					[0, 1, 2, 3], // vertexIndices
					materials.GreenDark,
					null, // textureUVs
					null // vertexNormals - todo
				),
			]
		);

		var textureUVsForMeshFaces = Mesh.TextureUVsDefault;

		var meshRainbowMonolith = new Mesh
		(
			"RainbowMonolith",
			// vertexPositions
			[
				// back 
				new Coords(-40, 0, -10), 
				new Coords(40, 0, -10),
				new Coords(40, -180, -10),
				new Coords(-40, -180, -10),

				// front
				new Coords(-40, 0, 10),
				new Coords(40, 0, 10),
				new Coords(40, -180, 10),
				new Coords(-40, -180, 10),
			],
			textureTextGL,
			// faces
			[
				new Face
				(
					[5, 4, 7, 6], 
					materials.Red, 
					textureUVsForMeshFaces
				), // front

				new Face
				(
					[0, 1, 2, 3], 
					materials.Orange, 
					textureUVsForMeshFaces
				), // back

				new Face
				(
					[3, 0, 4, 7], 
					materials.Yellow, 
					textureUVsForMeshFaces
				), // left

				new Face
				(
					[5, 6, 2, 1], 
					materials.Green, 
					textureUVsForMeshFaces
				), // right

				new Face
				(
					[6, 7, 3, 2], 
					materials.Blue, 
					textureUVsForMeshFaces
				), // top

				new Face
				(
					[4, 5, 1, 0], 
					materials.Violet, 
					textureUVsForMeshFaces
				), // bottom

			]
		);

		var bodyDefnGround = new BodyDefn
		(
			"Ground",
			meshGround
		);

		var bodyDefnRainbowMonolith = new BodyDefn
		(
			"RainbowMonolith",
			meshRainbowMonolith
		);		

		var bodyGround = new Body
		(
			"Ground0",
			bodyDefnGround,
			new Coords(0, 0, 0), // pos
			new Orientation
			(
				new Coords(0, 0, -1), // forward
				new Coords(1, 0, 0) // down
			)
		);

		var scene = new Scene
		(
			"Scene0",
			new Lighting
			(
				.5, // ambientIntensity
				new Coords(-1, -1, -1), // direction
				2 // directionalIntensity
			),
			new Camera
			(
				displaySize.clone(),
				displaySize.y / 2, // focalLength
				new Coords(-200, 0, -100), // pos
				new Orientation
				(
					new Coords(1, 0, 0), // forward
					new Coords(0, 0, 1) // down
				)
			),
			// textures
			[
				textureTextGL,
			],
			// bodies
			[
				bodyGround, 

				new Body
				(
					"RainbowMonolith0",
					bodyDefnRainbowMonolith,
					new Coords(0, 0, 0), // pos
					new Orientation
					(
						new Coords(1, 0, 0), // forward
						new Coords(0, 0, 1) // down
					)
				),

				new Body
				(
					"RainbowMonolith1",
					bodyDefnRainbowMonolith,
					new Coords(100, 0, 0), // pos
					new Orientation
					(
						new Coords(1, 0, 0), // forward
						new Coords(0, 0, 1) // down
					)
				),

				new Body
				(
					"RainbowMonolith2",
					bodyDefnRainbowMonolith,
					new Coords(0, 100, 0), // pos
					new Orientation
					(
						new Coords(1, 1, 0), // forward
						new Coords(0, 0, 1) // down
					)
				),
			]
		);

		return scene;
	}
}

// run

new WebGLTest().main();

</script>

<!-- WebGL shader programs -->

<div id="divShaderCodeFragment" style="display:none">

	precision mediump float;
	uniform sampler2D uSampler;
	varying vec4 vColor;
	varying vec3 vLight;
	varying vec2 vTextureUV;
	void main(void) 
	{
		if (vTextureUV.x < 0.0)
		{
			gl_FragColor = vColor;
		} 
		else 
		{
			vec4 textureColor = texture2D(uSampler, vec2(vTextureUV.s, vTextureUV.t));
			gl_FragColor = vec4(vLight * textureColor.rgb, textureColor.a);
		}
	}

</div>

<div id="divShaderCodeVertex" style="display:none">

	attribute vec4 aVertexColor;
	attribute vec3 aVertexNormal;
	attribute vec3 aVertexPosition;
	attribute vec2 aVertexTextureUV;
	uniform mat4 uBodyMatrix;
	uniform mat4 uCameraMatrix;
	uniform float uLightAmbientIntensity;
	uniform vec3 uLightDirection;
	uniform float uLightDirectionalIntensity;
	uniform mat4 uNormalMatrix;
	varying vec4 vColor;
	varying vec3 vLight;
	varying vec2 vTextureUV;
	void main(void) 
	{
		vColor = aVertexColor;
		vec4 vertexNormal4 = vec4(aVertexNormal, 0.0);
		vec4 transformedNormal4 = uNormalMatrix * vertexNormal4;
		vec3 transformedNormal = vec3(transformedNormal4.xyz) * -1.0;
		float lightMagnitude = uLightAmbientIntensity;
		lightMagnitude += 
			uLightDirectionalIntensity 
			* max(dot(transformedNormal, uLightDirection), 0.0);
		vLight = vec3(1.0, 1.0, 1.0) * lightMagnitude;
		vTextureUV = aVertexTextureUV;
		vec4 vertexPos = vec4(aVertexPosition, 1.0);
		gl_Position = uCameraMatrix * uBodyMatrix * vertexPos;
	}

</div>


</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