Exploring the COLLADA File Format in JavaScript

The JavaScript code below, when run, presents the user with a file upload button. When a .dae file is uploaded, it parses that file into a 3D scene and displays that scene as wireframes.

The program, as it stands, it nowhere near complete. The axes and transformations are all messed up, mostly because I’m not very good with matrix math, and some hacks have been introduced to work around this. No attempt is made to rotate any objects other than the camera. The camera’s field of view is wrong. No clipping is being done, so the camera shows things behind it as well as in front of it. The XML parsing is inelegant at best. But it’s good enough for me right now, because right now I’m really only interested in importing individual meshes that don’t have any transformations on them.

ColladaScene



<html>
<body>

<!-- user interface -->

<div id="divMain">
	<input id="inputFileToLoad" type="file" onchange="inputFileToLoad_Changed(this);" />
</div>

<script type="text/javascript">

// ui events

function inputFileToLoad_Changed(inputFileToLoad)
{
	var file = inputFileToLoad.files[0];
	var fileReader = new FileReader();
	fileReader.onload = inputFileToLoad_Changed_2;
	fileReader.readAsText(file, "UTF-8");
}

function inputFileToLoad_Changed_2(event)
{
	var fileReader = event.target;
	var fileContentsAsString = fileReader.result;

	var scene = ColladaFile.sceneFromStringXML
	(
		fileContentsAsString
	);

	Globals.Instance.initialize
	(
		scene
	);
}

// extensions

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

		return this;
	}
}

// classes

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.transformWorldToViewCoords = function(coordsToTransform)
	{
		coordsToTransform.subtract
		(
			this.pos
		).overwriteWithXYZ
		(
			coordsToTransform.dotProduct(this.orientation.right),
			coordsToTransform.dotProduct(this.orientation.down),
			coordsToTransform.dotProduct(this.orientation.forward)
		);

		var depth = coordsToTransform.z;

		coordsToTransform.multiplyScalar
		(
			this.focalLength
		).divideScalar
		(
			depth
		).add
		(
			this.viewSizeHalf
		);

		return coordsToTransform;
	}
}

function ColladaFile()
{
	// static class
}
{
	ColladaFile.sceneFromStringXML = function(colladaFileAsStringXML)
	{
		var parser = new DOMParser();
		var xmlDocument = parser.parseFromString
		(
			colladaFileAsStringXML, "text/xml"
		);

		var xmlElementRoot = xmlDocument.getElementsByTagName
		(
			"COLLADA"
		)[0];

		var xmlElementVisualScene = xmlDocument.getElementsByTagName
		(
			"library_visual_scenes"
		)[0].getElementsByTagName
		(
			"visual_scene"
		)[0];

		var camera = ColladaFile.sceneFromStringXML_Camera
		(
			xmlElementRoot,
			xmlElementVisualScene
		);

		var meshes = ColladaFile.sceneFromStringXML_Meshes
		(
			xmlElementRoot,
			xmlElementVisualScene
		);		

		var scene = new Scene(camera, meshes);

		return scene;
	}

	ColladaFile.sceneFromStringXML_Camera = function
	(
		xmlElementRoot, 
		xmlElementVisualScene
	)
	{
		var xmlElementCameraPerspective = xmlElementRoot.getElementsByTagName
		(
			"library_cameras"	
		)[0].getElementsByTagName
		(
			"camera"
		)[0].getElementsByTagName
		(
			"optics"
		)[0].getElementsByTagName
		(
			"technique_common"
		)[0].getElementsByTagName
		(
			"perspective"
		)[0];

		var cameraViewSize = new Coords(200, 200, 0);

		var aspectRatioAsString = xmlElementCameraPerspective.getElementsByTagName
		(
			"aspect_ratio"
		)[0].innerHTML;
		var aspectRatio = parseFloat(aspectRatioAsString);
		cameraViewSize.x *= aspectRatio;

		var widthOfViewInDegreesAsString = xmlElementCameraPerspective.getElementsByTagName
		(
			"xfov"
		)[0].innerHTML;
		var widthOfViewInDegrees = parseFloat(widthOfViewInDegreesAsString);
		var radiansPerDegree = 2 * Math.PI / 360;
		var widthOfViewInRadians = widthOfViewInDegrees * radiansPerDegree;
		var widthOfViewInRadiansHalf = widthOfViewInRadians / 2;
		var cameraFocalLength = 
			Math.cos(widthOfViewInRadiansHalf)
			* cameraViewSize.x / 2;

		var xmlElementsVisualSceneNode = xmlElementVisualScene.getElementsByTagName
		(
			"node"
		);

		var xmlElementCamera = null;

		for (var i = 0; i < xmlElementsVisualSceneNode.length; i++)
		{
			var xmlElementNode = xmlElementsVisualSceneNode[i];
			var nodeName = xmlElementNode.getAttribute("name");
			if (nodeName == "Camera")
			{
				xmlElementCamera = xmlElementNode;
				break;	
			}
		}

		var matrixForCameraTransformAsStrings = xmlElementCamera.getElementsByTagName
		(
			"matrix"
		)[0].innerHTML.split(" ");
		var matrixForCameraTransformAsNumbers = [];
		for (var i = 0; i < matrixForCameraTransformAsStrings.length; i++)
		{
			var valueAsString = matrixForCameraTransformAsStrings[i];
			matrixForCameraTransformAsNumbers[i] = parseFloat(valueAsString);
		}
		var matrixForCameraTransform = new Matrix
		(
			new Coords(4, 4), // size
			matrixForCameraTransformAsNumbers
		).transpose();

		var cameraPos = new Coords
		(
			matrixForCameraTransform.values[3],
			matrixForCameraTransform.values[7],
			matrixForCameraTransform.values[11]
		);

		var cameraForwardAndDown = 
		[
			new Coords(0, 0, -1),
			new Coords(0, -1, 0)
		];

		for (var i = 0; i < cameraForwardAndDown.length; i++)
		{
			var coordsToTransform = cameraForwardAndDown[i];
			var matrixToTransform = Matrix.fromCoords
			(
				coordsToTransform
			);
			var matrixTransformed = matrixForCameraTransform.multiply(matrixToTransform);
			var coordsTransformed = matrixTransformed.toCoords();
			coordsToTransform.overwriteWith(coordsTransformed);
		}

		var cameraOrientation = new Orientation
		(
			cameraForwardAndDown[0],
			cameraForwardAndDown[1]
		);
		
		var camera = new Camera
		(
			cameraViewSize,
			cameraFocalLength,
			cameraPos,
			cameraOrientation
		);

		return camera;
	}

	ColladaFile.sceneFromStringXML_Meshes = function
	(
		xmlElementRoot, 
		xmlElementVisualScene
	)
	{
		var meshes = [];

		var xmlElementsGeometry = xmlElementRoot.getElementsByTagName
		(
			"library_geometries"
		)[0].getElementsByTagName
		(
			"geometry"
		);

		for (var g = 0; g < xmlElementsGeometry.length; g++)
		{
			var xmlElementGeometry = xmlElementsGeometry[g];

			var mesh = ColladaFile.sceneFromStringXML_Meshes_Geometry
			(
				xmlElementGeometry
			);

			var xmlElementsVisualSceneNode = xmlElementVisualScene.getElementsByTagName
			(
				"node"
			);

			var xmlElementNodeForMesh = null;

			var meshNameEscaped = mesh.name.replace(/\./g, "_");

			for (var i = 0; i < xmlElementsVisualSceneNode.length; i++)
			{
				var xmlElementNode = xmlElementsVisualSceneNode[i];
				var nodeName = xmlElementNode.getAttribute("name");
				if (nodeName == meshNameEscaped)
				{
					xmlElementNodeForMesh = xmlElementNode;
					break;	
				}
			}

			var matrixValuesAsStrings = xmlElementNodeForMesh.getElementsByTagName
			(
				"matrix"
			)[0].innerHTML.split(" ");
			var matrixValues = [];
			for (var mv = 0; mv < matrixValuesAsStrings.length; mv++)
			{
				var matrixValueAsString = matrixValuesAsStrings[mv];
				var matrixValue = parseFloat(matrixValueAsString);
				matrixValues.push(matrixValue);
			}
			var matrix = new Matrix
			(
				new Coords(4, 4), // size
				matrixValues
			).transpose();
			var meshPos = new Coords
			(
				matrixValues[3],
				matrixValues[7],
				matrixValues[11]
			);	
			var transformTranslate = new Transform_Translate
			(
				meshPos
			);
			Transform.applyTransformToMesh(transformTranslate, mesh);
			// todo - Rotation.

			meshes.push(mesh);
		}

		return meshes;
	}

	ColladaFile.sceneFromStringXML_Meshes_Geometry = function(xmlElementGeometry)
	{
		var meshName = xmlElementGeometry.getAttribute("name");

		var vertices = ColladaFile.sceneFromStringXML_Meshes_Geometry_Vertices
		(
			xmlElementGeometry
		);

		var vertexIndicesForFaces = ColladaFile.sceneFromStringXML_Meshes_Geometry_Faces
		(
			xmlElementGeometry
		);
			
		var mesh = new Mesh
		(
			meshName,
			vertices,
			vertexIndicesForFaces
		);

		return mesh
	}

	ColladaFile.sceneFromStringXML_Meshes_Geometry_Vertices = function(xmlElementGeometry)
	{
		var vertices = [];
			
		var xmlElementsSource = xmlElementGeometry.getElementsByTagName
		(
			"mesh"
		)[0].getElementsByTagName
		(
			"source"
		);

		var geometryID = xmlElementGeometry.getAttribute("id");
		var sourceIDForVertexPositions = geometryID + "-positions";
		var xmlElementVertexPositions = null;

		for (var s = 0; s < xmlElementsSource.length; s++)
		{
			var xmlElementSource = xmlElementsSource[s];
			var sourceID = xmlElementSource.getAttribute("id");
			if (sourceID == sourceIDForVertexPositions)
			{
				xmlElementVertexPositions = xmlElementSource.getElementsByTagName
				(
					"float_array"
				)[0];
				break;
			}
		}

		var vertexPositionsAsString = xmlElementVertexPositions.innerHTML;
		var vertexPositionsAsStrings = vertexPositionsAsString.split(" ");

		for (var i = 0; i < vertexPositionsAsStrings.length; i += 3)
		{
			var vertexPos = new Coords
			(
				parseFloat(vertexPositionsAsStrings[i]),
				parseFloat(vertexPositionsAsStrings[i + 1]),
				parseFloat(vertexPositionsAsStrings[i + 2])
			);

			var vertex = new Vertex(vertexPos);

			vertices.push(vertex);
		}

		return vertices;
	}

	ColladaFile.sceneFromStringXML_Meshes_Geometry_Faces = function(xmlElementGeometry)
	{
		var xmlElementForFaces = xmlElementGeometry.getElementsByTagName
		(
			"polylist"
		)[0];

		var numberOfFaces = xmlElementForFaces.getAttribute("count");

		var xmlElementsInput = xmlElementForFaces.getElementsByTagName("input");
		var numberOfInputs = xmlElementsInput.length;
		var offsetOfVertexIndex = null;

		for (var i = 0; i < xmlElementsInput.length; i++)
		{
			var xmlElementInput = xmlElementsInput[i];
			var semantic = xmlElementInput.getAttribute("semantic");
			if (semantic == "VERTEX")
			{
				offsetOfVertexIndex = parseInt
				(
					xmlElementInput.getAttribute("offset")
				);
			}
		}

		var faceVertexCounts = xmlElementForFaces.getElementsByTagName
		(
			"vcount"
		)[0].innerHTML.split(" ");

		var faceVertexIndices = xmlElementForFaces.getElementsByTagName
		(
			"p"
		)[0].innerHTML.split(" ");

		var vertexIndicesForFaces = [];
		var vertexIndexOffsetForFace = 0;
			
		for (var f = 0; f < numberOfFaces; f++)
		{
			var vertexIndicesForFace = [];

			var numberOfVerticesInFace = faceVertexCounts[f];

			for (var vi = 0; vi < numberOfVerticesInFace; vi++)
			{					
				var viAdjusted = 
					vi * numberOfInputs 
					+ offsetOfVertexIndex
					+ vertexIndexOffsetForFace;

				var vertexIndex = faceVertexIndices[viAdjusted];

				vertexIndicesForFace.push(vertexIndex);
			}

			vertexIndicesForFaces.push(vertexIndicesForFace);

			vertexIndexOffsetForFace += 
				numberOfVerticesInFace * numberOfInputs;
		}

		return vertexIndicesForFaces;

	}
}

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

	Coords.prototype.clear = function()
	{
		this.x = 0;
		this.y = 0;
		this.z = 0;
		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 - other.y * this.z,
			other.x * this.z - this.x * other.z,
			this.x * other.y - other.x * this.y
		);
	}
	
	Coords.prototype.divideScalar = function(scalar)
	{
		this.x /= scalar;
		this.y /= scalar;
		this.z /= scalar;
		return this;
	}

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

	Coords.prototype.equals = function(other)
	{
		var returnValue = 
		(
			this.x == other.x
			&& this.y == other.y
			&& this.z == other.z
		);
		
		return returnValue;
	}

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

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

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

		this.graphics.strokeStyle = "Gray";
		this.graphics.strokeRect
		(
			0,
			0,
			this.viewSize.x,
			this.viewSize.y
		);
	}

	DisplayHelper.prototype.drawMeshForCamera = function(mesh, camera)
	{
		this.graphics.strokeStyle = mesh.color;

		var drawPos = new Coords();

		var drawEdges = true;
		if (drawEdges == true)
		for (var e = 0; e < mesh.edges.length; e++)
		{
			var edge = mesh.edges[e];
			
			var vertexIndexStart = edge.vertexIndices[0];
			var vertexIndexEnd = edge.vertexIndices[1];
			
			var vertexStart = mesh.vertices[vertexIndexStart];
			var vertexEnd = mesh.vertices[vertexIndexEnd];

			var vertexStartPos = vertexStart.pos;
			var vertexEndPos = vertexEnd.pos;

			this.graphics.beginPath();
				
			drawPos.overwriteWith(vertexStartPos);
			camera.transformWorldToViewCoords(drawPos);

			this.graphics.moveTo(drawPos.x, drawPos.y);

			drawPos.overwriteWith(vertexEndPos);
			camera.transformWorldToViewCoords(drawPos);
			this.graphics.lineTo(drawPos.x, drawPos.y);
			this.graphics.stroke();					
		}

		var drawVertices = true;
		if (drawVertices == true)
		{
			for (var v = 0; v < mesh.vertices.length; v++)
			{
				var vertex = mesh.vertices[v];
				var vertexPos = vertex.pos;
				drawPos.overwriteWith(vertexPos);
				camera.transformWorldToViewCoords(drawPos);
				this.graphics.beginPath();
				this.graphics.arc
				(
					drawPos.x, drawPos.y, // center
					3, // radius
					0, Math.PI * 2 // start and stop angles
				);
				this.graphics.stroke();
			}
		}
	}

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

		for (var m = 0; m < scene.meshes.length; m++)
		{
			var mesh = scene.meshes[m];
			this.drawMeshForCamera(mesh, scene.camera);
		}
	}

	DisplayHelper.prototype.initialize = function()
	{
		var canvas = document.createElement("canvas");
		canvas.width = this.viewSize.x;
		canvas.height = this.viewSize.y;
		
		this.graphics = canvas.getContext("2d");
		
		var divMain = document.getElementById("divMain");
		divMain.appendChild(canvas);
	}
}

function Edge(vertexIndices)
{
	this.vertexIndices = vertexIndices;
	this.faceIndices = [];
}
{
	Edge.prototype.midpoint = function(mesh)
	{
		var returnValue = mesh.vertices[this.vertexIndices[0]].pos.clone().add
		(
			mesh.vertices[this.vertexIndices[1]].pos
		).divideScalar(2);

		return returnValue;
	}

	Edge.prototype.vertexPositions = function(mesh)
	{
		var returnValue = 
		[
			mesh.vertices[this.vertexIndices[0]].pos,
			mesh.vertices[this.vertexIndices[1]].pos,
		];

		return returnValue;
	}
}

function Face(vertexIndices)
{
	this.vertexIndices = vertexIndices;
	this.edgeIndices = [];
}

function Globals()
{}
{
	// instance

	Globals.Instance = new Globals();

	// methods

	Globals.prototype.initialize = function(scene)
	{
		this.scene = scene;

		var viewSize = this.scene.camera.viewSize;
		this.displayHelper = new DisplayHelper(viewSize);
		this.displayHelper.initialize();
		this.displayHelper.drawScene(this.scene);

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

function InputHelper()
{}
{
	InputHelper.prototype.initialize = function()
	{
		document.body.onkeydown = this.handleEventKeyDown.bind(this);
		document.body.onkeyup = this.handleEventKeyUp.bind(this);
	}

	// events

	InputHelper.prototype.handleEventKeyDown = function(event)
	{
		this.keyCodePressed = event.keyCode;

		var scene = Globals.Instance.scene;
		var camera = scene.camera;
		var distanceToMove = .1;
		var amountToTurn = .1;

		if (this.keyCodePressed == 65) // A
		{
			camera.pos.subtract
			(
				camera.orientation.right.clone().multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (this.keyCodePressed == 67) // C
		{
			camera.orientation.forward.add
			(
				camera.orientation.right.clone().multiplyScalar
				(
					amountToTurn
				)
			);

			camera.orientation.orthogonalizeAxes();
		}
		else if (this.keyCodePressed == 68) // D
		{
			camera.pos.add
			(
				camera.orientation.right.clone().multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (this.keyCodePressed == 69) // E
		{
			camera.orientation.down.add
			(
				camera.orientation.right.clone().multiplyScalar
				(
					amountToTurn
				)
			);

			camera.orientation.orthogonalizeAxes();
		}
		else if (this.keyCodePressed == 70) // F
		{
			camera.pos.add
			(
				camera.orientation.down.clone().multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (this.keyCodePressed == 81) // Q
		{
			camera.orientation.down.subtract
			(
				camera.orientation.right.clone().multiplyScalar
				(
					amountToTurn
				)
			);

			camera.orientation.orthogonalizeAxes();
		}
		else if (this.keyCodePressed == 82) // R
		{
			camera.pos.subtract
			(
				camera.orientation.down.clone().multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (this.keyCodePressed == 83) // S
		{
			camera.pos.subtract
			(
				camera.orientation.forward.clone().multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (this.keyCodePressed == 87) // W
		{
			camera.pos.add
			(
				camera.orientation.forward.clone().multiplyScalar
				(
					distanceToMove
				)
			);
		}
		else if (this.keyCodePressed == 90) // Z
		{
			camera.orientation.forward.subtract
			(
				camera.orientation.right.clone().multiplyScalar
				(
					amountToTurn
				)
			);

			camera.orientation.orthogonalizeAxes();
		}

		Globals.Instance.displayHelper.drawScene(scene);
	}

	InputHelper.prototype.handleEventKeyUp = function(event)
	{
		this.keyCodePressed = null;
	}
}

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

	Matrix.fromCoords = function(coordsToConvert)
	{
		return new Matrix
		(
			new Coords(1, 4),
			[
				coordsToConvert.x, 
				coordsToConvert.y, 
				coordsToConvert.z, 
				1
			]
		);
	}

	// instance methods

	Matrix.prototype.clone = function()
	{
		var valuesCloned = this.values.slice(0);

		var returnValue = new Matrix(this.size, 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.equals = function(other)
	{
		var returnValue = false;

		if (this.size.equals(other.size) == true)
		{
			returnValue = true;

			for (var i = 0; i < this.values.length; i++)
			{
				var valueThis = this.values[i];	
				var valueOther = other.values[i];
				if (valueThis != valueOther)
				{
					returnValue = false;
					break;
				}
			}
		}

		return returnValue;
	}

	Matrix.prototype.multiply = function(other)
	{
		if (this.size.x != other.size.y)
		{
			throw "Matrices cannot be multiplied!";
		}

		var sizeOfProduct = new Coords(other.size.x, this.size.y);

		var valuesMultiplied = [];

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

				for (var i = 0; i < other.size.y; i++)
				{
					valueSoFar += 
						this.values[y * this.size.x + i]
						* other.values[i * other.size.x + x];
				}

				valuesMultiplied.push(valueSoFar);
			}
		}

		var returnValue = new Matrix(sizeOfProduct, valuesMultiplied);

		return returnValue;
	}

	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.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.toCoords = function()
	{
		return new Coords(this.values[0], this.values[1], this.values[2]);
	}

	Matrix.prototype.transpose = function()
	{
		var valuesTransposed = [];

		for (var x = 0; x < this.size.x; x++)
		{
			for (var y = 0; y < this.size.y; y++)
			{
				var valueIndex = x * this.size.y + y;
				var value = this.values[valueIndex];
				valuesTransposed.push(value);
			}
		}

		var temp = this.size.x;
		this.size.x = this.size.y;
		this.size.y = temp;

		this.values = valuesTransposed;

		return this;
	}
}

function Mesh(name, vertices, vertexIndicesForFaces)
{
	this.name = name;
	this.vertices = vertices;

	this.color = "Blue";

	this.faces = [];

	this.edges = [];
	var vertexIndicesMinMaxToEdgeIndexLookup = [];

	for (var f = 0; f < vertexIndicesForFaces.length; f++)
	{
		var vertexIndicesForFace = vertexIndicesForFaces[f];
		var numberOfVerticesInFace = vertexIndicesForFace.length; 

		var face = new Face(vertexIndicesForFace);

		for (var vi = 0; vi < numberOfVerticesInFace; vi++)
		{
			var viNext = (vi + 1) % numberOfVerticesInFace;

			var vertexIndex = vertexIndicesForFace[vi];
			var vertexIndexNext = vertexIndicesForFace[viNext];

			var vertex = this.vertices[vertexIndex];
			var vertexNext = this.vertices[vertexIndexNext];

			vertex.faceIndices.push(f);

			var vertexIndexMin = Math.min(vertexIndex, vertexIndexNext);
			var vertexIndexMax = Math.max(vertexIndex, vertexIndexNext);

			var vertexIndexMaxToEdgeIndexLookup = 
				vertexIndicesMinMaxToEdgeIndexLookup[vertexIndexMin];

			if (vertexIndexMaxToEdgeIndexLookup == null)
			{
				vertexIndexMaxToEdgeIndexLookup = [];
				vertexIndicesMinMaxToEdgeIndexLookup[vertexIndexMin] =
					vertexIndexMaxToEdgeIndexLookup;
			}

			var edgeIndex = vertexIndexMaxToEdgeIndexLookup[vertexIndexMax];

			if (edgeIndex == null)
			{
				var edge = new Edge([vertexIndexMin, vertexIndexMax]);
				edgeIndex = this.edges.length;
				this.edges.push(edge);
			}

			vertexIndexMaxToEdgeIndexLookup[vertexIndexMax] = edgeIndex;

			// hack
			// Is there away to avoid this indexOf call?

			if (face.edgeIndices.indexOf(edgeIndex) == -1)
			{
				face.edgeIndices.push(edgeIndex);
			}
		}

		for (var ei = 0; ei < face.edgeIndices.length; ei++)
		{
			var edgeIndex = face.edgeIndices[ei];
			var edge = this.edges[edgeIndex];
			edge.faceIndices.push(f);

			for (var vi = 0; vi < edge.vertexIndices.length; vi++)
			{
				var vertexIndex = edge.vertexIndices[vi];
				var vertex = this.vertices[vertexIndex];

				// hack
				// Is there away to avoid this indexOf call?
				if (vertex.edgeIndices.indexOf(edgeIndex) == -1)
				{
					vertex.edgeIndices.push(edgeIndex);
				}
			}
		}

		this.faces.push(face);
	}

} // end Mesh constructor

function Orientation(forward, down)
{
	this.forward = forward.clone().normalize();
	this.down = down.clone().normalize();
	this.right = new Coords();
	this.orthogonalizeAxes();
}
{
	Orientation.prototype.orthogonalizeAxes = function()
	{
		this.right.overwriteWith
		(
			this.down
		).normalize().crossProduct
		(
			this.forward
		).normalize();
	
		this.down.overwriteWith
		(
			this.forward
		).crossProduct
		(
			this.right
		).normalize();
	}
}

function Scene(camera, meshes)
{
	this.camera = camera;
	this.meshes = meshes;
}

function Transform()
{
	// abstract class
}
{
	Transform.applyTransformToMesh = function(transform, mesh)
	{
		var vertices = mesh.vertices;

		for (var i = 0; i < vertices.length; i++)
		{
			var vertex = vertices[i];
			transform.applyToCoords(vertex.pos);
		}
	}
}

function Transform_Translate(displacement)
{
	this.displacement = displacement;
}
{
	Transform_Translate.prototype.applyToCoords = function(coordsToTransform)
	{
		coordsToTransform.add(this.displacement);
	}	
}

function Vertex(pos)
{
	this.pos = pos;
	this.edgeIndices = [];
	this.faceIndices = [];
}
{
	Vertex.manyFromPositions = function(positions)
	{
		var returnValues = [];

		for (var i = 0; i < positions.length; i++)
		{
			var position = positions[i];
			var vertex = new Vertex(position);
			returnValues.push(vertex);
		}

		return returnValues;
	}
}

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