program Pas2JS_WebGL_OBJ;
uses
	Types, Mat4, MemoryBuffer, GLUtils, GLTypes, SysUtils,
	BrowserConsole, Web, WebGL, JS, Math;

var
  gl: TJSWebGLRenderingContext;
  shader: TShader;
  projTransform: TMat4;
  viewTransform: TMat4;
  modelTransform: TMat4;

var
	dragonModel: TModel = nil;
	rotateAngle: double = 0;
	deltaTime: TJSFloat32 = 0;
	nextTime: TJSFloat32 = 0;

procedure DrawCanvas;
begin
	gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT);

	if dragonModel <> nil then
		begin
			modelTransform := TMat4.Identity;
			modelTransform := modelTransform.Multiply(TMat4.RotateY(DegToRad(rotateAngle)));

			shader.SetUniformMat4('modelTransform', modelTransform);

			dragonModel.Draw;
		end;
end;

procedure AnimateCanvas(time: TJSDOMHighResTimeStamp);
var
	now: TJSFloat32;
begin
	now := time * 0.001;
	deltaTime := now - nextTime;
	nextTime := now;
	rotateAngle := rotateAngle + (20 * deltaTime);

	DrawCanvas;

	window.requestAnimationFrame(@AnimateCanvas);
end;

procedure StartAnimatingCanvas;
begin
	window.requestAnimationFrame(@AnimateCanvas);
end;

type
	TModelLoader = class
		public
			constructor Create (context: TJSWebGLRenderingContext; path: string);
		private
			gl: TJSWebGLRenderingContext; 
			request: TJSXMLHttpRequest;

			procedure HandleLoaded;
	end;

procedure TModelLoader.HandleLoaded;
var
	data: TModelData;
begin
	if (request.readyState = 4) and (request.status = 200) and (length(request.responseText) > 0) then
		begin
			data := LoadOBJFile(TJSString(request.responseText));
			dragonModel := TModel.Create(gl, data);
			StartAnimatingCanvas;
		end;
end;

constructor TModelLoader.Create (context: TJSWebGLRenderingContext; path: string);
begin
	gl := context;

	request := TJSXMLHttpRequest.new;
	request.open('GET', path);
	request.overrideMimeType('application/text');
	request.onreadystatechange := TJSOnReadyStateChangeHandler(@HandleLoaded);
	request.send;
end;

var
  canvas: TJSHTMLCanvasElement;
  vertexShaderSource: string;
  fragmentShaderSource: string;
begin

	// make webgl context
  canvas := TJSHTMLCanvasElement(document.createElement('canvas'));
  canvas.width := 600;
  canvas.height := 600;
	document.body.appendChild(canvas);

	gl := TJSWebGLRenderingContext(canvas.getContext('webgl'));
	if gl = nil then
		begin
			writeln('failed to load webgl!');
			exit;
		end;

	// create shaders from source in html
	// TODO: move these to .glsl files so error messages make more sense
	// and give valid line numbers
	vertexShaderSource := document.getElementById('vertex.glsl').textContent;
	fragmentShaderSource := document.getElementById('fragment.glsl').textContent;

	shader := TShader.Create(gl, vertexShaderSource, fragmentShaderSource);
	shader.Compile;
  shader.BindAttribLocation(0, 'in_position');
  shader.BindAttribLocation(1, 'in_texCoord');
  shader.BindAttribLocation(2, 'in_normal');
	shader.Link;
	shader.Use;

	// prepare context
	gl.clearColor(0.9, 0.9, 0.9, 1);
	gl.viewport(0, 0, canvas.width, canvas.height);
	gl.clear(gl.COLOR_BUFFER_BIT);

	gl.enable(gl.DEPTH_TEST);
	gl.enable(gl.BLEND);
	gl.Enable(gl.CULL_FACE);
	gl.CullFace(gl.BACK);

	// set projection transform
	projTransform := TMat4.Perspective(60.0, canvas.width / canvas.height, 0.1, 2000);
	shader.SetUniformMat4('projTransform', projTransform);

	// set view transform
	viewTransform := TMat4.Identity;
	viewTransform := viewTransform.Multiply(TMat4.Translate(0, -3, -20));
	shader.SetUniformMat4('viewTransform', viewTransform);

	// NOTE: webgl glsl doesn't have the inverse function
	// so we need to do this here
	shader.SetUniformMat4('inverseViewTransform', viewTransform.Inverse);

	// lighting
	shader.SetUniformVec3('lightPosition', V3(0, 0, 25));
	shader.SetUniformVec3('lightColor', V3(1, 1, 1));

	// model material
	shader.SetUniformFloat('shineDamper', 10);
	shader.SetUniformFloat('reflectivity', 1);

	// load obj file 
	TModelLoader.Create(gl, 'res/dragon.obj');
end.
