瀏覽代碼

Terrain demo

Ryan Joseph 7 年之前
父節點
當前提交
76857328f8
共有 15 個文件被更改,包括 3432 次插入121 次删除
  1. 156 15
      GLTypes.pas
  2. 68 34
      GLUtils.pas
  3. 71 0
      Matrix.pas
  4. 36 3
      MemoryBuffer.pas
  5. 244 0
      Noise.pas
  6. 28 9
      Pas2JS_WebGL.js
  7. 8 3
      Pas2JS_WebGL.pas
  8. 91 56
      Pas2JS_WebGL_OBJ.js
  9. 1 1
      Pas2JS_WebGL_OBJ.pas
  10. 2245 0
      Pas2JS_WebGL_Terrain.js
  11. 229 0
      Pas2JS_WebGL_Terrain.pas
  12. 159 0
      Terrain.pas
  13. 96 0
      html/Pas2JS_WebGL_Terrain.html
  14. 二進制
      html/res/ground.jpg
  15. 二進制
      html/res/spacestars.jpg

+ 156 - 15
GLTypes.pas

@@ -1,37 +1,178 @@
 unit GLTypes;
 unit GLTypes;
 interface
 interface
 uses
 uses
-	WebGL, JS;
+	WebGL, JS, Math, SysUtils;
 
 
-// TODO: when advanced record syntax is added move these to records
+type
+	TScalar = single;
+
+type
+	TVec2 = record
+		x, y: TScalar;
+	end;
+
+type
+	TVec3 = record
+		x, y, z: TScalar;
+	end;
 
 
 type
 type
-	TVec2 = array of GLfloat;
-	TVec3 = array of GLfloat;
 	TRGBAb = array of GLubyte;
 	TRGBAb = array of GLubyte;
-	TRGBAf = array of GLfloat;
+	TRGBAf = array of TScalar;
 
 
-function V2(x, y: GLfloat): TVec2;
-function V3(x, y, z: GLfloat): TVec3;
+{ Vec3 }
+function V3(x, y, z: TScalar): TVec3;
+function ToFloats(v: TVec3): TJSFloat32List; overload;
+function VecStr(v: TVec3): string; overload;
+procedure Show (v: TVec3);
+
+// math functions since we don't have advanced records
+function Add (v: TVec3; amount: TScalar): TVec3; overload;
+function Add (v: TVec3; amount: TVec3): TVec3; overload;
+function Subtract (v: TVec3; amount: TScalar): TVec3; overload;
+function Subtract (v: TVec3; amount: TVec3): TVec3; overload;
+function Multiply (v: TVec3; amount: TScalar): TVec3; overload;
+function Multiply (v: TVec3; amount: TVec3): TVec3; overload;
+function Divide (v: TVec3; amount: TScalar): TVec3; overload;
+function Divide (v: TVec3; amount: TVec3): TVec3; overload;
+function Sum (v: TVec3): TScalar; overload;
+function Magnitude(v: TVec3): TScalar; overload;
+function SquaredLength (v: TVec3): TScalar; overload;
+function Normalize (v: TVec3): TVec3; overload;
+function Dot (v: TVec3; point: TVec3): TScalar; overload;
+function Cross (v: TVec3; point: TVec3): TVec3; overload;
+
+{ Vec2 }
+function V2(x, y: TScalar): TVec2;
+function ToFloats(v: TVec2): TJSFloat32List; overload;
 
 
 function RGBAb(r, g, b, a: GLubyte): TRGBAb;
 function RGBAb(r, g, b, a: GLubyte): TRGBAb;
-function RGBAf(r, g, b, a: GLfloat): TRGBAf;
+function RGBAf(r, g, b, a: TScalar): TRGBAf;
 
 
 implementation
 implementation
 
 
-function V2(x, y: GLfloat): TVec2;
+{=============================================}
+{@! ___Vec2___ } 
+{=============================================}
+
+function V2(x, y: TScalar): TVec2;
 begin
 begin
-	result[0] := x;
-	result[1] := y;
+	result.x := x;
+	result.y := y;
 end;
 end;
 
 
-function V3(x, y, z: GLfloat): TVec2;
+function ToFloats(v: TVec2): TJSFloat32List;
 begin
 begin
-	result[0] := x;
-	result[1] := y;
-	result[2] := z;
+	SetLength(result, 2);
+	result[0] := v.x;
+	result[1] := v.y;
 end;
 end;
 
 
+{=============================================}
+{@! ___Vec3___ } 
+{=============================================}
+
+function V3(x, y, z: TScalar): TVec3;
+begin
+	result.x := x;
+	result.y := y;
+	result.z := z;
+end;
+
+function Add (v: TVec3; amount: TScalar): TVec3;
+begin
+	result := V3(v.x + amount, v.y + amount, v.z + amount);
+end;
+
+function Add (v: TVec3; amount: TVec3): TVec3;
+begin
+	result := V3(v.x + amount.x, v.y + amount.y, v.z + amount.z);
+end;
+
+function Subtract (v: TVec3; amount: TScalar): TVec3;
+begin
+	result := V3(v.x - amount, v.y - amount, v.z - amount);
+end;
+
+function Subtract (v: TVec3; amount: TVec3): TVec3;
+begin
+	result := V3(v.x - amount.x, v.y - amount.y, v.z - amount.z);
+end;
+
+function Multiply (v: TVec3; amount: TScalar): TVec3;
+begin
+	result := V3(v.x * amount, v.y * amount, v.z * amount);
+end;
+
+function Multiply (v: TVec3; amount: TVec3): TVec3;
+begin
+	result := V3(v.x * amount.x, v.y * amount.y, v.z * amount.z);
+end;
+
+function Divide (v: TVec3; amount: TScalar): TVec3;
+begin
+	result := V3(v.x / amount, v.y / amount, v.z / amount);
+end;
+
+function Divide (v: TVec3; amount: TVec3): TVec3;
+begin
+	result := V3(v.x / amount.x, v.y / amount.y, v.z / amount.z);
+end;
+
+function Sum (v: TVec3): TScalar;
+begin
+	result := v.x + v.y + v.z;
+end;
+
+function Magnitude(v: TVec3): TScalar;
+begin
+	result := Sqrt(Power(v.x, 2) + Power(v.y, 2) + Power(v.z, 2));
+end;
+
+function SquaredLength (v: TVec3): TScalar;
+begin
+	result := Sqr(v.x) + Sqr(v.y) + Sqr(v.z);
+end;
+
+function Normalize (v: TVec3): TVec3;
+begin
+	result := Divide(v, Magnitude(v));
+end;
+
+function Dot (v: TVec3; point: TVec3): TScalar;
+begin
+	result := (v.x * point.x) + (v.y * point.y) + (v.z * point.z);
+end;
+
+function Cross (v: TVec3; point: TVec3): TVec3;
+begin
+	result.x := (v.y * point.z) - (v.z * point.y);
+	result.y := (v.z * point.x) - (v.x * point.z);
+	result.z := (v.x * point.y) - (v.y * point.x);
+end;
+
+function ToFloats(v: TVec3): TJSFloat32List;
+begin
+	SetLength(result, 3);
+	result[0] := v.x;
+	result[1] := v.y;
+	result[2] := v.z;
+end;
+
+function VecStr(v: TVec3): string;
+begin
+	result := '{'+FloatToStr(v.x)+','+FloatToStr(v.y)+','+FloatToStr(v.z)+'}';
+end;
+
+procedure Show (v: TVec3);
+begin
+	writeln('{',v.x,',',v.y,',',v.z,'}');
+end;
+
+{=============================================}
+{@! ___Colors___ } 
+{=============================================}
+
 function RGBAb(r, g, b, a: GLubyte): TRGBAb;
 function RGBAb(r, g, b, a: GLubyte): TRGBAb;
 begin
 begin
 	result[0] := r;
 	result[0] := r;

+ 68 - 34
GLUtils.pas

@@ -1,9 +1,9 @@
 unit GLUtils;
 unit GLUtils;
 interface
 interface
 uses
 uses
-	Mat4, GLTypes, Types,
+	MemoryBuffer, Mat4, GLTypes,
 	BrowserConsole, Web, WebGL, JS, 
 	BrowserConsole, Web, WebGL, JS, 
-	Math, SysUtils;
+	Types, Math, SysUtils;
 
 
 type
 type
 	TShader = class
 	TShader = class
@@ -32,33 +32,48 @@ type
 
 
 
 
 type
 type
-	TOBJData = record
+	TModelData = record
 		verticies: TJSFloat32Array;		// GLfloat
 		verticies: TJSFloat32Array;		// GLfloat
 
 
 		// NOTE: it's not clear if WebGL supports GLuint
 		// NOTE: it's not clear if WebGL supports GLuint
 		// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements
 		// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawElements
 
 
-		indices: TJSUint16Array;			// GLushort
+		indicies: TJSUint16Array;			// GLushort
 		floatsPerVertex: integer;
 		floatsPerVertex: integer;
 	end;
 	end;
 
 
+const
+	kModelVertexFloats = 3 + 2 + 3;
+type
+	TModelVertex = record
+		pos: TVec3;
+		texCoord: TVec2;
+		normal: TVec3;
+	end;
+
+procedure ModelVertexAddToBuffer(vertex: TModelVertex; buffer: TMemoryBuffer);
+procedure ModelVertexAddToArray (vertex: TModelVertex; list: TJSArray); 
+
 type
 type
 	TModel = class
 	TModel = class
 		public
 		public
-			constructor Create(context: TJSWebGLRenderingContext; objData: TOBJData);
+			constructor Create(context: TJSWebGLRenderingContext; modelData: TModelData); overload;
 			procedure Draw;
 			procedure Draw;
 		private
 		private
 			gl: TJSWebGLRenderingContext;
 			gl: TJSWebGLRenderingContext;
-			data: TOBJData;
+			data: TModelData;
 			vertexBuffer: TJSWebGLBuffer;
 			vertexBuffer: TJSWebGLBuffer;
 			indexBuffer: TJSWebGLBuffer;
 			indexBuffer: TJSWebGLBuffer;
+			elementCount: integer;
 
 
 			procedure EnableAttributes;
 			procedure EnableAttributes;
 			procedure Load;
 			procedure Load;
 	end;
 	end;
 
 
-function LoadOBJFile (text: TJSString): TOBJData;
+function LoadOBJFile (text: TJSString): TModelData;
+
 function GLSizeof(glType: NativeInt): integer; 
 function GLSizeof(glType: NativeInt): integer; 
+procedure GLFatal (gl: TJSWebGLRenderingContext; messageString: string = 'Fatal OpenGL error'); 
 
 
 implementation
 implementation
 
 
@@ -69,14 +84,14 @@ implementation
 procedure Fatal (messageString: string); overload;
 procedure Fatal (messageString: string); overload;
 begin
 begin
 	writeln('*** FATAL: ', messageString);
 	writeln('*** FATAL: ', messageString);
-	exit;
+	raise Exception.Create('FATAL');
 end;
 end;
 
 
 // TODO: toll free bridge to FPC strings
 // TODO: toll free bridge to FPC strings
 procedure Fatal (messageString: TJSString); overload;
 procedure Fatal (messageString: TJSString); overload;
 begin
 begin
 	writeln('*** FATAL: ', messageString);
 	writeln('*** FATAL: ', messageString);
-	exit;
+	raise Exception.Create('FATAL');
 end;
 end;
 
 
 procedure GLFatal (gl: TJSWebGLRenderingContext; messageString: string = 'Fatal OpenGL error'); 
 procedure GLFatal (gl: TJSWebGLRenderingContext; messageString: string = 'Fatal OpenGL error'); 
@@ -97,8 +112,6 @@ begin
 				otherwise
 				otherwise
 					messageString := messageString+' '+IntToStr(error);
 					messageString := messageString+' '+IntToStr(error);
 			end;
 			end;
-			// TODO: IntoStr doesn't work? cast to string or TJSString doesn't work either
-			//messageString := messageString+' '+IntToStr(error);
 			Fatal(messageString);
 			Fatal(messageString);
 		end;
 		end;
 end;
 end;
@@ -122,10 +135,32 @@ end;
 {=============================================}
 {=============================================}
 {@! ___Model___ } 
 {@! ___Model___ } 
 {=============================================}
 {=============================================}
-constructor TModel.Create(context: TJSWebGLRenderingContext; objData: TOBJData);
+
+procedure ModelVertexAddToBuffer(vertex: TModelVertex; buffer: TMemoryBuffer);
+begin
+	buffer.AddFloats(kModelVertexFloats, [
+		vertex.pos.x, vertex.pos.y, vertex.pos.z,
+		vertex.texCoord.x, vertex.texCoord.y,
+		vertex.normal.x, vertex.normal.y, vertex.normal.z
+		]);
+end;
+
+procedure ModelVertexAddToArray (vertex: TModelVertex; list: TJSArray); 
+begin
+	list.push(vertex.pos.x);
+	list.push(vertex.pos.y);
+	list.push(vertex.pos.z);
+	list.push(vertex.texCoord.x);
+	list.push(vertex.texCoord.y);
+	list.push(vertex.normal.x);
+	list.push(vertex.normal.y);
+	list.push(vertex.normal.z);
+end;
+
+constructor TModel.Create(context: TJSWebGLRenderingContext; modelData: TModelData);
 begin
 begin
 	gl := context;
 	gl := context;
-	data := objData;
+	data := modelData;
 	Load;
 	Load;
 end;
 end;
 
 
@@ -135,7 +170,7 @@ begin
 	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
 	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
 
 
 	EnableAttributes;
 	EnableAttributes;
-	gl.drawElements(gl.TRIANGLES, data.indices.length, gl.UNSIGNED_SHORT, 0);
+	gl.drawElements(gl.TRIANGLES, data.indicies.length, gl.UNSIGNED_SHORT, 0);
 
 
 	gl.bindBuffer(gl.ARRAY_BUFFER, nil);
 	gl.bindBuffer(gl.ARRAY_BUFFER, nil);
 	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, nil);
 	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, nil);
@@ -169,12 +204,10 @@ begin
 end;
 end;
 
 
 procedure TModel.Load;
 procedure TModel.Load;
-var
-	i: integer;
 begin
 begin
 	indexBuffer := gl.createBuffer;
 	indexBuffer := gl.createBuffer;
 	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
 	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
-	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data.indices, gl.STATIC_DRAW);	
+	gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data.indicies, gl.STATIC_DRAW);	
 	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, nil);
 	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, nil);
 
 
 	vertexBuffer := gl.createBuffer;
 	vertexBuffer := gl.createBuffer;
@@ -221,7 +254,7 @@ begin
 	result := vertex;
 	result := vertex;
 end;
 end;
 
 
-function LoadOBJFile (text: TJSString): TOBJData;
+function LoadOBJFile (text: TJSString): TModelData;
 const
 const
 	kLineEnding = #10;
 	kLineEnding = #10;
 	kSpace = ' '; // what code is space?
 	kSpace = ' '; // what code is space?
@@ -239,7 +272,7 @@ var
 	line: TJSString;
 	line: TJSString;
 	vertex: TOBJVertex;
 	vertex: TOBJVertex;
 	vertexIndex: integer;
 	vertexIndex: integer;
-	objData: TOBJData;
+	data: TModelData;
 
 
 	pos: TVec3;
 	pos: TVec3;
 	texCoord: TVec2;
 	texCoord: TVec2;
@@ -286,28 +319,28 @@ begin
 		end;
 		end;
 	
 	
 	// vec3 (position) + vec2 (texCoord) + vec3 (normal)
 	// vec3 (position) + vec2 (texCoord) + vec3 (normal)
-	objData.floatsPerVertex := 3 + 2 + 3;
+	data.floatsPerVertex := kModelVertexFloats;
 
 
-	mesh := TJSFloat32Array.New(objData.floatsPerVertex * verticies.length);
+	mesh := TJSFloat32Array.New(data.floatsPerVertex * verticies.length);
 
 
 	for i := 0 to verticies.length - 1 do
 	for i := 0 to verticies.length - 1 do
 		begin
 		begin
 			vertex := TOBJVertex(verticies[i]);
 			vertex := TOBJVertex(verticies[i]);
 
 
-			vertexIndex := i * objData.floatsPerVertex;
+			vertexIndex := i * data.floatsPerVertex;
 
 
 			// position
 			// position
 			pos := TVec3(positions[i]);
 			pos := TVec3(positions[i]);
-			mesh[vertexIndex + 0] := pos[0];
-			mesh[vertexIndex + 1] := pos[1];
-			mesh[vertexIndex + 2] := pos[2];
+			mesh[vertexIndex + 0] := pos.x;
+			mesh[vertexIndex + 1] := pos.y;
+			mesh[vertexIndex + 2] := pos.z;
 
 
 			// texture
 			// texture
 			if vertex.textureIndex <> -1 then
 			if vertex.textureIndex <> -1 then
 				begin
 				begin
 					texCoord := TVec2(textures[vertex.textureIndex]);
 					texCoord := TVec2(textures[vertex.textureIndex]);
-					mesh[vertexIndex + 3] := texCoord[0];
-					mesh[vertexIndex + 4] := texCoord[1];
+					mesh[vertexIndex + 3] := texCoord.x;
+					mesh[vertexIndex + 4] := texCoord.y;
 				end
 				end
 			else
 			else
 				begin
 				begin
@@ -319,9 +352,9 @@ begin
 			if vertex.normalIndex <> -1 then
 			if vertex.normalIndex <> -1 then
 				begin
 				begin
 					normal := TVec3(normals[vertex.normalIndex]);
 					normal := TVec3(normals[vertex.normalIndex]);
-					mesh[vertexIndex + 5] := normal[0];
-					mesh[vertexIndex + 6] := normal[1];
-					mesh[vertexIndex + 7] := normal[2];
+					mesh[vertexIndex + 5] := normal.x;
+					mesh[vertexIndex + 6] := normal.y;
+					mesh[vertexIndex + 7] := normal.z;
 				end;
 				end;
 		end;
 		end;
 
 
@@ -329,10 +362,10 @@ begin
 	//writeln('positions:', positions.length);
 	//writeln('positions:', positions.length);
 	//writeln('indices:', indices.length);
 	//writeln('indices:', indices.length);
 
 
-	objData.verticies := mesh;
-	objData.indices := TJSUint16Array.New(TJSObject(indices));
+	data.verticies := mesh;
+	data.indicies := TJSUint16Array.New(TJSObject(indices));
 
 
-	result := objData;
+	result := data;
 end;
 end;
 
 
 {=============================================}
 {=============================================}
@@ -353,7 +386,8 @@ end;
 
 
 procedure TShader.SetUniformVec3 (name: string; value: TVec3);
 procedure TShader.SetUniformVec3 (name: string; value: TVec3);
 begin
 begin
-	gl.uniform3fv(GetUniformLocation(name), TJSFloat32List(value));
+	//gl.uniform3fv(GetUniformLocation(name), ToFloats(value));
+	gl.uniform3f(GetUniformLocation(name), value.x, value.y, value.z);
 	GLFatal(gl, 'gl.uniform3fv');
 	GLFatal(gl, 'gl.uniform3fv');
 end;
 end;
 
 

+ 71 - 0
Matrix.pas

@@ -0,0 +1,71 @@
+unit Matrix;
+interface
+uses
+	JS;
+
+type
+	TMatrix = class
+		public
+			constructor Create (w, h: integer);
+			procedure SetValue(x, y: integer; value: JSValue);
+			function GetValue(x, y: integer): JSValue;
+			procedure Show;
+			function GetWidth: integer;
+			function GetHeight: integer;
+			// NOTE: no indexers yet?
+			//property Indexer[const x,y:integer]:JSValue read GetValue write SetValue; default;
+		private
+			table: TJSArray;
+			width: integer;
+			height: integer;
+
+			function IndexFor(x, y: integer): integer;
+	end;
+
+implementation
+
+constructor TMatrix.Create (w, h: integer);
+begin
+	width := w;
+	height := h;
+	table := TJSArray.new(width * height);
+end;
+
+procedure TMatrix.SetValue(x, y: integer; value: JSValue);
+begin
+	table[IndexFor(x, y)] := value;
+end;
+
+function TMatrix.GetValue(x, y: integer): JSValue;
+begin
+	result := table[IndexFor(x, y)];
+end;
+
+function TMatrix.IndexFor(x, y: integer): integer;
+begin
+	result := x + y * height;
+end;
+
+procedure TMatrix.Show;
+var
+	x, y: integer;
+begin
+	for x := 0 to width - 1 do
+	for y := 0 to height - 1 do
+		begin
+			writeln(x,',',y, ': ', GetValue(x, y));
+		end;
+end;
+
+function TMatrix.GetWidth: integer;
+begin
+	result := width;
+end;
+
+function TMatrix.GetHeight: integer;
+begin
+	result := height;
+end;
+
+
+end.

+ 36 - 3
MemoryBuffer.pas

@@ -11,12 +11,22 @@ type
 			byteBuffer: TJSUint8Array;
 			byteBuffer: TJSUint8Array;
 		public
 		public
 		 constructor Create (size: integer);
 		 constructor Create (size: integer);
+
+		 { UInt8 }
 		 procedure AddBytes (count: integer; data: array of byte);
 		 procedure AddBytes (count: integer; data: array of byte);
+
+		 { Float32 }
 		 procedure AddFloats (count: integer; data: array of single);
 		 procedure AddFloats (count: integer; data: array of single);
+		 procedure AddFloat (data: single);
+
+		 { UInt16 }
+		 procedure AddWords(count: integer; data: array of word);
+
 		 property GetBytes: TJSUint8Array read byteBuffer;
 		 property GetBytes: TJSUint8Array read byteBuffer;
 		private
 		private
 			byteOffset: integer;
 			byteOffset: integer;
 			floatBuffer: TJSFloat32Array;
 			floatBuffer: TJSFloat32Array;
+			wordBuffer: TJSUInt16Array;
 	end;
 	end;
 
 
 implementation
 implementation
@@ -33,19 +43,42 @@ begin
 	byteOffset := byteOffset + (count * 1);
 	byteOffset := byteOffset + (count * 1);
 end;
 end;
 
 
+procedure TMemoryBuffer.AddFloat (data: single);
+begin
+	AddFloats(1, [data]);
+end;
+
 procedure TMemoryBuffer.AddFloats (count: integer; data: array of single);
 procedure TMemoryBuffer.AddFloats (count: integer; data: array of single);
+const
+	kElementSize = 4;
 var
 var
 	floatOffset: integer;
 	floatOffset: integer;
 begin
 begin
-	floatOffset := byteOffset div 4;
+	floatOffset := byteOffset div kElementSize;
 	//writeln('AddFloats: @', byteOffset, '/', floatOffset, ' -> ', data);
 	//writeln('AddFloats: @', byteOffset, '/', floatOffset, ' -> ', data);
 
 
 	if floatBuffer = nil then
 	if floatBuffer = nil then
-		floatBuffer := TJSFloat32Array.New(byteBuffer.buffer, 0, byteBuffer.byteLength div 4);
+		floatBuffer := TJSFloat32Array.New(byteBuffer.buffer, 0, byteBuffer.byteLength div kElementSize);
 
 
 	floatBuffer._set(data, floatOffset);
 	floatBuffer._set(data, floatOffset);
 
 
-	byteOffset := byteOffset + (count * 4);
+	byteOffset := byteOffset + (count * kElementSize);
+end;
+
+procedure TMemoryBuffer.AddWords(count: integer; data: array of word);
+const
+	kElementSize = 2;
+var
+	wordOffset: integer;
+begin
+	wordOffset := byteOffset div kElementSize;
+
+	if wordBuffer = nil then
+		wordBuffer := TJSUInt16Array.New(byteBuffer.buffer, 0, byteBuffer.byteLength div kElementSize);
+
+	wordBuffer._set(data, wordOffset);
+
+	byteOffset := byteOffset + (count * kElementSize);
 end;
 end;
 
 
 end.
 end.

+ 244 - 0
Noise.pas

@@ -0,0 +1,244 @@
+unit Noise;
+interface
+uses
+	SysUtils, Math;
+
+const
+	kNoisekPerumationMax = 256;
+
+type
+	TNoiseValue = byte;
+	TNoiseSeedArray = array[0..kNoisekPerumationMax-1] of TNoiseValue;
+	TNoiseFloat = double;
+	
+type
+	TNoise = class (TObject)
+		public
+			constructor Create; overload;
+			constructor Create (seed: TNoiseSeedArray); overload;
+			
+			function GetValue (x, y, z: TNoiseFloat): TNoiseFloat; overload;
+			function GetValue (x, y, z: TNoiseFloat; octaves: integer; persistence: TNoiseFloat): TNoiseFloat; overload;
+			function GetNoise (x, y: integer; width, height: integer; scale: TNoiseFloat; frequency: integer): TNoiseFloat; overload;
+
+		private
+			repeatValue: integer;
+			p: array[0..(kNoisekPerumationMax * 2)-1] of TNoiseValue;
+			
+			function Inc (num: integer): integer; inline;
+			function Grad (hash: integer; x, y, z: TNoiseFloat): TNoiseFloat; inline;
+			function Fade (t: TNoiseFloat): TNoiseFloat; inline;
+			function Lerp (a, b, x: TNoiseFloat): TNoiseFloat; inline;
+	end;
+
+function RandomNoiseSeed (seed: cardinal = 0): TNoiseSeedArray;
+
+implementation
+
+
+function RandomNoiseSeed (seed: cardinal = 0): TNoiseSeedArray; 
+var
+	i: integer;
+begin		
+	for i := 0 to kNoisekPerumationMax - 1 do
+		result[i] := Random(kNoisekPerumationMax);
+end;
+
+function TNoise.GetValue (x, y, z: TNoiseFloat; octaves: integer; persistence: TNoiseFloat): TNoiseFloat;
+var
+	total: TNoiseFloat = 0;
+	frequency: TNoiseFloat = 1;
+	amplitude: TNoiseFloat = 1;
+	maxValue: TNoiseFloat = 0; // Used for normalizing result to 0.0 - 1.0
+	i: integer;
+begin
+	for i := 0 to octaves - 1 do
+		begin
+			total += GetValue(x * frequency, y * frequency, z * frequency) * amplitude;
+			maxValue += amplitude;
+			amplitude *= persistence;
+      frequency *= 2;
+		end;
+	result := total/maxValue;
+end;
+
+function TNoise.GetNoise (x, y: integer; width, height: integer; scale: TNoiseFloat; frequency: integer): TNoiseFloat; 
+var
+	nx, ny: TNoiseFloat;
+begin
+	nx := x/width - 0.5; 
+	ny := y/height - 0.5;
+	result := GetValue(nx * scale, ny * scale, 0, frequency, 0.5) / 2 + 0.5;
+end;
+
+function TNoise.GetValue (x, y, z: TNoiseFloat): TNoiseFloat;
+function FMod(const a, b: TNoiseFloat): TNoiseFloat;
+begin
+  result:= a-b * trunc(a/b);
+end;
+var
+	xi, yi, zi: integer;
+	xf, yf, zf: TNoiseFloat;
+	u, v, w: TNoiseFloat;
+	aaa, aba, aab, abb, baa, bba, bab, bbb: integer;
+	x1, x2, y1, y2: TNoiseFloat;
+begin
+	// If we have any repeat on, change the coordinates to their "local" repetitions
+	if (repeatValue > 0) then			
+		begin
+			x := FMod(x, repeatValue);
+			y := FMod(y, repeatValue);
+			z := FMod(z, repeatValue);
+			// ??? mod overloading for singles in trunk 3.1.1
+			{x := x mod repeatValue;
+			y := y mod repeatValue;
+			z := z mod repeatValue;}
+		end;
+	
+	xi := Floor(x) and 255;								// Calculate the "unit cube" that the point asked will be located in
+	yi := Floor(y) and 255;								// The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
+	zi := Floor(z) and 255;								// plus 1.  Next we calculate the location (from 0.0 to 1.0) in that cube.
+	xf := x-Floor(x);											// We also fade the location to smooth the result.
+	yf := y-Floor(y);
+	zf := z-Floor(z);
+	u := Fade(xf);
+	v := Fade(yf);
+	w := Fade(zf);
+	
+	aaa := p[p[p[    xi ]+    yi ]+    zi ];
+	aba := p[p[p[    xi ]+self.Inc(yi)]+    zi ];
+	aab := p[p[p[    xi ]+    yi ]+self.Inc(zi)];
+	abb := p[p[p[    xi ]+self.Inc(yi)]+self.Inc(zi)];
+	baa := p[p[p[self.Inc(xi)]+    yi ]+    zi ];
+	bba := p[p[p[self.Inc(xi)]+self.Inc(yi)]+    zi ];
+	bab := p[p[p[self.Inc(xi)]+    yi ]+self.Inc(zi)];
+	bbb := p[p[p[self.Inc(xi)]+self.Inc(yi)]+self.Inc(zi)];
+	
+	x1 := Lerp(	Grad(aaa, xf  , yf  , zf),				// The gradient function calculates the dot product between a pseudorandom
+				Grad(baa, xf-1, yf  , zf),							// gradient vector and the vector from the input coordinate to the 8
+				u);																			// surrounding points in its unit cube.
+	x2 := Lerp(	Grad(aba, xf  , yf-1, zf),				// This is all then lerped together as a sort of weighted average based on the faded (u,v,w)
+				Grad(bba, xf-1, yf-1, zf),							// values we made earlier.
+		          u);
+	y1 := Lerp(x1, x2, v);
+
+	x1 := Lerp(	Grad(aab, xf  , yf  , zf-1),
+				Grad(bab, xf-1, yf  , zf-1),
+				u);
+	x2 := Lerp(	Grad(abb, xf  , yf-1, zf-1),
+	          	Grad(bbb, xf-1, yf-1, zf-1),
+	          	u);
+	y2 := Lerp(x1, x2, v);
+	
+	result := (Lerp(y1, y2, w)+1)/2;						// For convenience we bound it to 0 - 1 (theoretical min/max before is -1 - 1)
+end;
+
+function TNoise.Inc (num: integer): integer;
+begin
+	num += 1;
+	if repeatValue > 0 then
+		num := num mod repeatValue;
+	result := num;
+end;
+
+// http://riven8192.blogspot.com/2010/08/calculate-perlinnoise-twice-as-fast.html
+function TNoise.Grad (hash: integer; x, y, z: TNoiseFloat): TNoiseFloat;
+begin
+	case (hash and $F) of
+		$0:
+			result := x + y;
+		$1:
+			result := -x + y;
+		$2:
+			result := x - y;
+		$3:
+			result := -x - y;
+		$4:
+			result := x + z;
+		$5:
+			result := -x + z;
+		$6:
+			result := x - z;
+		$7:
+			result := -x - z;
+		$8:
+			result := y + z;
+		$9:
+			result := -y + z;
+		$A:
+			result := y - z;
+		$B:
+			result := -y - z;
+		$C:
+			result := y + x;
+		$D:
+			result := -y + z;
+		$E:
+			result := y - x;
+		$F:
+			result :=	-y - z;
+		otherwise
+			result := 0; // never happens
+	end;
+end;
+
+{
+function TNoise.Grad (hash: integer; x, y, z: TNoiseFloat): TNoiseFloat;
+var
+	h: integer;
+	u, v: TNoiseFloat;
+begin
+	h := hash and 15;									// Take the hashed value and take the first 4 bits of it (15 == 0b1111)
+	
+	if h < 8 then
+		u := x
+	else
+		u := y;
+
+	if h < 4 then
+		v := y
+	else if (h = 12) or (h = 14) then
+		v := x
+	else
+		v := z;
+	
+	if h and 1 = 0 then
+		result := u
+	else
+		result := -u;
+		
+	if h and 2 = 0 then
+		result := result + v
+	else
+		result := result - v;
+end;
+}
+
+function TNoise.Fade (t: TNoiseFloat): TNoiseFloat; 
+begin
+	// Fade function as defined by Ken Perlin.  This eases coordinate values
+	// so that they will "ease" towards integral values.  This ends up smoothing
+	// the final output.
+	result := t * t * t * (t * (t * 6 - 15) + 10);			// 6t^5 - 15t^4 + 10t^3
+end;
+
+function TNoise.Lerp (a, b, x: TNoiseFloat): TNoiseFloat; 
+begin
+	result := a + x * (b - a);
+end;
+
+constructor TNoise.Create;
+begin	
+	Create(RandomNoiseSeed);
+end;
+
+constructor TNoise.Create (seed: TNoiseSeedArray);
+var
+	i: integer;
+begin	
+	repeatValue := -1;
+	for i := 0 to high(p) do
+		p[i] := seed[i mod kNoisekPerumationMax];
+end;
+
+end.

+ 28 - 9
Pas2JS_WebGL.js

@@ -1502,10 +1502,29 @@ rtl.module("webgl",["System","JS","Web"],function () {
 rtl.module("GLTypes",["System","webgl","JS"],function () {
 rtl.module("GLTypes",["System","webgl","JS"],function () {
   "use strict";
   "use strict";
   var $mod = this;
   var $mod = this;
+  this.TVec2 = function (s) {
+    if (s) {
+      this.x = s.x;
+      this.y = s.y;
+    } else {
+      this.x = 0.0;
+      this.y = 0.0;
+    };
+    this.$equal = function (b) {
+      return (this.x === b.x) && (this.y === b.y);
+    };
+  };
   this.V2 = function (x, y) {
   this.V2 = function (x, y) {
+    var Result = new $mod.TVec2();
+    Result.x = x;
+    Result.y = y;
+    return Result;
+  };
+  this.ToFloats$1 = function (v) {
     var Result = [];
     var Result = [];
-    Result[0] = x;
-    Result[1] = y;
+    Result = rtl.arraySetLength(Result,0.0,2);
+    Result[0] = v.x;
+    Result[1] = v.y;
     return Result;
     return Result;
   };
   };
   this.RGBAb = function (r, g, b, a) {
   this.RGBAb = function (r, g, b, a) {
@@ -1636,14 +1655,14 @@ rtl.module("program",["System","Mat4","MemoryBuffer","GLUtils","GLTypes","SysUti
   var $mod = this;
   var $mod = this;
   this.GLVertex2 = function (s) {
   this.GLVertex2 = function (s) {
     if (s) {
     if (s) {
-      this.pos = s.pos;
+      this.pos = new pas.GLTypes.TVec2(s.pos);
       this.color = s.color;
       this.color = s.color;
     } else {
     } else {
-      this.pos = [];
+      this.pos = new pas.GLTypes.TVec2();
       this.color = [];
       this.color = [];
     };
     };
     this.$equal = function (b) {
     this.$equal = function (b) {
-      return (this.pos === b.pos) && (this.color === b.color);
+      return this.pos.$equal(b.pos) && (this.color === b.color);
     };
     };
   };
   };
   this.kSIZEOF_VERTEX = 12;
   this.kSIZEOF_VERTEX = 12;
@@ -1654,20 +1673,20 @@ rtl.module("program",["System","Mat4","MemoryBuffer","GLUtils","GLTypes","SysUti
     var v = new $mod.GLVertex2();
     var v = new $mod.GLVertex2();
     var i = 0;
     var i = 0;
     verts = new Array();
     verts = new Array();
-    v.pos = pas.GLTypes.V2(0,0);
+    v.pos = new pas.GLTypes.TVec2(pas.GLTypes.V2(0,0));
     v.color = pas.GLTypes.RGBAb(255,0,0,255);
     v.color = pas.GLTypes.RGBAb(255,0,0,255);
     verts.push(new $mod.GLVertex2(v));
     verts.push(new $mod.GLVertex2(v));
-    v.pos = pas.GLTypes.V2(0,100);
+    v.pos = new pas.GLTypes.TVec2(pas.GLTypes.V2(0,100));
     v.color = pas.GLTypes.RGBAb(0,255,0,255);
     v.color = pas.GLTypes.RGBAb(0,255,0,255);
     verts.push(new $mod.GLVertex2(v));
     verts.push(new $mod.GLVertex2(v));
-    v.pos = pas.GLTypes.V2(100,100);
+    v.pos = new pas.GLTypes.TVec2(pas.GLTypes.V2(100,100));
     v.color = pas.GLTypes.RGBAb(0,0,255,255);
     v.color = pas.GLTypes.RGBAb(0,0,255,255);
     verts.push(new $mod.GLVertex2(v));
     verts.push(new $mod.GLVertex2(v));
     buffer = pas.MemoryBuffer.TMemoryBuffer.$create("Create$1",[12 * verts.length]);
     buffer = pas.MemoryBuffer.TMemoryBuffer.$create("Create$1",[12 * verts.length]);
     for (var $l1 = 0, $end2 = verts.length - 1; $l1 <= $end2; $l1++) {
     for (var $l1 = 0, $end2 = verts.length - 1; $l1 <= $end2; $l1++) {
       i = $l1;
       i = $l1;
       v = new $mod.GLVertex2(rtl.getObject(verts[i]));
       v = new $mod.GLVertex2(rtl.getObject(verts[i]));
-      buffer.AddFloats(2,v.pos);
+      buffer.AddFloats(2,pas.GLTypes.ToFloats$1(new pas.GLTypes.TVec2(v.pos)));
       buffer.AddBytes(4,v.color);
       buffer.AddBytes(4,v.color);
     };
     };
     Result = buffer.byteBuffer;
     Result = buffer.byteBuffer;

+ 8 - 3
Pas2JS_WebGL.pas

@@ -10,7 +10,7 @@ type
 	end;
 	end;
 
 
 const
 const
-	kSIZEOF_VERTEX = 12;
+	kSIZEOF_VERTEX = 12; 	// vec2 + RGBAb
 
 
 function GetVertexData: TJSUInt8Array;
 function GetVertexData: TJSUInt8Array;
 var
 var
@@ -19,6 +19,12 @@ var
 	v: GLVertex2;
 	v: GLVertex2;
 	i: integer;
 	i: integer;
 begin
 begin
+
+	// there's really no reason to build the array
+	// as vectors first then pack into bytes but
+	// we're doing it anyways because this how most
+	// will be familar with vertex data from standard
+	// OpenGL
 	verts := TJSArray.new;
 	verts := TJSArray.new;
 
 
 	v.pos := V2(0, 0);
 	v.pos := V2(0, 0);
@@ -38,7 +44,7 @@ begin
 	for i := 0 to verts.length - 1 do
 	for i := 0 to verts.length - 1 do
 		begin
 		begin
 			v := GLVertex2(verts[i]);
 			v := GLVertex2(verts[i]);
-			buffer.AddFloats(2, v.pos);
+			buffer.AddFloats(2, {v.pos}ToFloats(v.pos));
 			buffer.AddBytes(4, v.color);
 			buffer.AddBytes(4, v.color);
 		end;
 		end;
 
 
@@ -137,7 +143,6 @@ begin
   gl.bufferData(gl.ARRAY_BUFFER, GetVertexData, gl.STATIC_DRAW);
   gl.bufferData(gl.ARRAY_BUFFER, GetVertexData, gl.STATIC_DRAW);
 
 
 	offset := 0;  
 	offset := 0;  
-	// vec2 + RGBAb
 	stride := kSIZEOF_VERTEX;
 	stride := kSIZEOF_VERTEX;
 
 
 	// position
 	// position

+ 91 - 56
Pas2JS_WebGL_OBJ.js

@@ -2210,24 +2210,58 @@ rtl.module("webgl",["System","JS","Web"],function () {
   "use strict";
   "use strict";
   var $mod = this;
   var $mod = this;
 });
 });
-rtl.module("GLTypes",["System","webgl","JS"],function () {
+rtl.module("GLTypes",["System","webgl","JS","math"],function () {
   "use strict";
   "use strict";
   var $mod = this;
   var $mod = this;
-  this.V2 = function (x, y) {
-    var Result = [];
-    Result[0] = x;
-    Result[1] = y;
-    return Result;
+  this.TVec2 = function (s) {
+    if (s) {
+      this.x = s.x;
+      this.y = s.y;
+    } else {
+      this.x = 0.0;
+      this.y = 0.0;
+    };
+    this.$equal = function (b) {
+      return (this.x === b.x) && (this.y === b.y);
+    };
+  };
+  this.TVec3 = function (s) {
+    if (s) {
+      this.x = s.x;
+      this.y = s.y;
+      this.z = s.z;
+    } else {
+      this.x = 0.0;
+      this.y = 0.0;
+      this.z = 0.0;
+    };
+    this.$equal = function (b) {
+      return (this.x === b.x) && ((this.y === b.y) && (this.z === b.z));
+    };
   };
   };
   this.V3 = function (x, y, z) {
   this.V3 = function (x, y, z) {
+    var Result = new $mod.TVec3();
+    Result.x = x;
+    Result.y = y;
+    Result.z = z;
+    return Result;
+  };
+  this.ToFloats = function (v) {
     var Result = [];
     var Result = [];
-    Result[0] = x;
-    Result[1] = y;
-    Result[2] = z;
+    Result = rtl.arraySetLength(Result,0.0,3);
+    Result[0] = v.x;
+    Result[1] = v.y;
+    Result[2] = v.z;
+    return Result;
+  };
+  this.V2 = function (x, y) {
+    var Result = new $mod.TVec2();
+    Result.x = x;
+    Result.y = y;
     return Result;
     return Result;
   };
   };
 });
 });
-rtl.module("GLUtils",["System","Mat4","GLTypes","Types","browserconsole","Web","webgl","JS","math","SysUtils"],function () {
+rtl.module("GLUtils",["System","Mat4","GLTypes","browserconsole","Web","webgl","JS","Types","math","SysUtils"],function () {
   "use strict";
   "use strict";
   var $mod = this;
   var $mod = this;
   var $impl = $mod.$impl;
   var $impl = $mod.$impl;
@@ -2275,7 +2309,7 @@ rtl.module("GLUtils",["System","Mat4","GLTypes","Types","browserconsole","Web","
       $impl.GLFatal(this.gl,"gl.uniformMatrix4fv");
       $impl.GLFatal(this.gl,"gl.uniformMatrix4fv");
     };
     };
     this.SetUniformVec3 = function (name, value) {
     this.SetUniformVec3 = function (name, value) {
-      this.gl.uniform3fv(this.GetUniformLocation(name),value);
+      this.gl.uniform3fv(this.GetUniformLocation(name),pas.GLTypes.ToFloats(new pas.GLTypes.TVec3(value)));
       $impl.GLFatal(this.gl,"gl.uniform3fv");
       $impl.GLFatal(this.gl,"gl.uniform3fv");
     };
     };
     this.SetUniformFloat = function (name, value) {
     this.SetUniformFloat = function (name, value) {
@@ -2303,25 +2337,26 @@ rtl.module("GLUtils",["System","Mat4","GLTypes","Types","browserconsole","Web","
       return Result;
       return Result;
     };
     };
   });
   });
-  this.TOBJData = function (s) {
+  this.TModelData = function (s) {
     if (s) {
     if (s) {
       this.verticies = s.verticies;
       this.verticies = s.verticies;
-      this.indices = s.indices;
+      this.indicies = s.indicies;
       this.floatsPerVertex = s.floatsPerVertex;
       this.floatsPerVertex = s.floatsPerVertex;
     } else {
     } else {
       this.verticies = null;
       this.verticies = null;
-      this.indices = null;
+      this.indicies = null;
       this.floatsPerVertex = 0;
       this.floatsPerVertex = 0;
     };
     };
     this.$equal = function (b) {
     this.$equal = function (b) {
-      return (this.verticies === b.verticies) && ((this.indices === b.indices) && (this.floatsPerVertex === b.floatsPerVertex));
+      return (this.verticies === b.verticies) && ((this.indicies === b.indicies) && (this.floatsPerVertex === b.floatsPerVertex));
     };
     };
   };
   };
+  this.kModelVertexFloats = (3 + 2) + 3;
   rtl.createClass($mod,"TModel",pas.System.TObject,function () {
   rtl.createClass($mod,"TModel",pas.System.TObject,function () {
     this.$init = function () {
     this.$init = function () {
       pas.System.TObject.$init.call(this);
       pas.System.TObject.$init.call(this);
       this.gl = null;
       this.gl = null;
-      this.data = new $mod.TOBJData();
+      this.data = new $mod.TModelData();
       this.vertexBuffer = null;
       this.vertexBuffer = null;
       this.indexBuffer = null;
       this.indexBuffer = null;
     };
     };
@@ -2332,16 +2367,16 @@ rtl.module("GLUtils",["System","Mat4","GLTypes","Types","browserconsole","Web","
       this.indexBuffer = undefined;
       this.indexBuffer = undefined;
       pas.System.TObject.$final.call(this);
       pas.System.TObject.$final.call(this);
     };
     };
-    this.Create$1 = function (context, objData) {
+    this.Create$1 = function (context, modelData) {
       this.gl = context;
       this.gl = context;
-      this.data = new $mod.TOBJData(objData);
+      this.data = new $mod.TModelData(modelData);
       this.Load();
       this.Load();
     };
     };
     this.Draw = function () {
     this.Draw = function () {
       this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuffer);
       this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuffer);
       this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,this.indexBuffer);
       this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,this.indexBuffer);
       this.EnableAttributes();
       this.EnableAttributes();
-      this.gl.drawElements(this.gl.TRIANGLES,this.data.indices.length,this.gl.UNSIGNED_SHORT,0);
+      this.gl.drawElements(this.gl.TRIANGLES,this.data.indicies.length,this.gl.UNSIGNED_SHORT,0);
       this.gl.bindBuffer(this.gl.ARRAY_BUFFER,null);
       this.gl.bindBuffer(this.gl.ARRAY_BUFFER,null);
       this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,null);
       this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,null);
     };
     };
@@ -2363,7 +2398,7 @@ rtl.module("GLUtils",["System","Mat4","GLTypes","Types","browserconsole","Web","
     this.Load = function () {
     this.Load = function () {
       this.indexBuffer = this.gl.createBuffer();
       this.indexBuffer = this.gl.createBuffer();
       this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,this.indexBuffer);
       this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,this.indexBuffer);
-      this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER,this.data.indices,this.gl.STATIC_DRAW);
+      this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER,this.data.indicies,this.gl.STATIC_DRAW);
       this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,null);
       this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,null);
       this.vertexBuffer = this.gl.createBuffer();
       this.vertexBuffer = this.gl.createBuffer();
       this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuffer);
       this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuffer);
@@ -2374,7 +2409,7 @@ rtl.module("GLUtils",["System","Mat4","GLTypes","Types","browserconsole","Web","
   var kLineEnding = "\n";
   var kLineEnding = "\n";
   var kSpace = " ";
   var kSpace = " ";
   this.LoadOBJFile = function (text) {
   this.LoadOBJFile = function (text) {
-    var Result = new $mod.TOBJData();
+    var Result = new $mod.TModelData();
     var lines = [];
     var lines = [];
     var parts = [];
     var parts = [];
     var indices = null;
     var indices = null;
@@ -2387,10 +2422,10 @@ rtl.module("GLUtils",["System","Mat4","GLTypes","Types","browserconsole","Web","
     var line = null;
     var line = null;
     var vertex = new $impl.TOBJVertex();
     var vertex = new $impl.TOBJVertex();
     var vertexIndex = 0;
     var vertexIndex = 0;
-    var objData = new $mod.TOBJData();
-    var pos = [];
-    var texCoord = [];
-    var normal = [];
+    var data = new $mod.TModelData();
+    var pos = new pas.GLTypes.TVec3();
+    var texCoord = new pas.GLTypes.TVec2();
+    var normal = new pas.GLTypes.TVec3();
     positions = new Array();
     positions = new Array();
     normals = new Array();
     normals = new Array();
     textures = new Array();
     textures = new Array();
@@ -2402,50 +2437,50 @@ rtl.module("GLUtils",["System","Mat4","GLTypes","Types","browserconsole","Web","
       line = lines[i];
       line = lines[i];
       parts = line.split(kSpace);
       parts = line.split(kSpace);
       if (line.startsWith("v ")) {
       if (line.startsWith("v ")) {
-        pos = pas.GLTypes.V3(pas.SysUtils.StrToFloat(parts[1]),pas.SysUtils.StrToFloat(parts[2]),pas.SysUtils.StrToFloat(parts[3]));
-        positions.push(pos);
-        vertex.position = pos;
+        pos = new pas.GLTypes.TVec3(pas.GLTypes.V3(pas.SysUtils.StrToFloat(parts[1]),pas.SysUtils.StrToFloat(parts[2]),pas.SysUtils.StrToFloat(parts[3])));
+        positions.push(new pas.GLTypes.TVec3(pos));
+        vertex.position = new pas.GLTypes.TVec3(pos);
         vertex.textureIndex = -1;
         vertex.textureIndex = -1;
         vertex.normalIndex = -1;
         vertex.normalIndex = -1;
-        verticies.push(pos);
+        verticies.push(new pas.GLTypes.TVec3(pos));
       } else if (line.startsWith("vn ")) {
       } else if (line.startsWith("vn ")) {
-        normals.push(pas.GLTypes.V3(pas.SysUtils.StrToFloat(parts[1]),pas.SysUtils.StrToFloat(parts[2]),pas.SysUtils.StrToFloat(parts[3])));
+        normals.push(new pas.GLTypes.TVec3(pas.GLTypes.V3(pas.SysUtils.StrToFloat(parts[1]),pas.SysUtils.StrToFloat(parts[2]),pas.SysUtils.StrToFloat(parts[3]))));
       } else if (line.startsWith("vt ")) {
       } else if (line.startsWith("vt ")) {
-        textures.push(pas.GLTypes.V2(pas.SysUtils.StrToFloat(parts[1]),1 - pas.SysUtils.StrToFloat(parts[2])));
+        textures.push(new pas.GLTypes.TVec2(pas.GLTypes.V2(pas.SysUtils.StrToFloat(parts[1]),1 - pas.SysUtils.StrToFloat(parts[2]))));
       } else if (line.startsWith("f ")) {
       } else if (line.startsWith("f ")) {
         $impl.ProcessFace(verticies,indices,parts[1].split("\/"));
         $impl.ProcessFace(verticies,indices,parts[1].split("\/"));
         $impl.ProcessFace(verticies,indices,parts[2].split("\/"));
         $impl.ProcessFace(verticies,indices,parts[2].split("\/"));
         $impl.ProcessFace(verticies,indices,parts[3].split("\/"));
         $impl.ProcessFace(verticies,indices,parts[3].split("\/"));
       };
       };
     };
     };
-    objData.floatsPerVertex = (3 + 2) + 3;
-    mesh = new Float32Array(objData.floatsPerVertex * verticies.length);
+    data.floatsPerVertex = 8;
+    mesh = new Float32Array(data.floatsPerVertex * verticies.length);
     for (var $l3 = 0, $end4 = verticies.length - 1; $l3 <= $end4; $l3++) {
     for (var $l3 = 0, $end4 = verticies.length - 1; $l3 <= $end4; $l3++) {
       i = $l3;
       i = $l3;
       vertex = new $impl.TOBJVertex(rtl.getObject(verticies[i]));
       vertex = new $impl.TOBJVertex(rtl.getObject(verticies[i]));
-      vertexIndex = i * objData.floatsPerVertex;
-      pos = positions[i];
-      mesh[vertexIndex + 0] = pos[0];
-      mesh[vertexIndex + 1] = pos[1];
-      mesh[vertexIndex + 2] = pos[2];
+      vertexIndex = i * data.floatsPerVertex;
+      pos = new pas.GLTypes.TVec3(rtl.getObject(positions[i]));
+      mesh[vertexIndex + 0] = pos.x;
+      mesh[vertexIndex + 1] = pos.y;
+      mesh[vertexIndex + 2] = pos.z;
       if (vertex.textureIndex !== -1) {
       if (vertex.textureIndex !== -1) {
-        texCoord = textures[vertex.textureIndex];
-        mesh[vertexIndex + 3] = texCoord[0];
-        mesh[vertexIndex + 4] = texCoord[1];
+        texCoord = new pas.GLTypes.TVec2(rtl.getObject(textures[vertex.textureIndex]));
+        mesh[vertexIndex + 3] = texCoord.x;
+        mesh[vertexIndex + 4] = texCoord.y;
       } else {
       } else {
         mesh[vertexIndex + 3] = 0;
         mesh[vertexIndex + 3] = 0;
         mesh[vertexIndex + 4] = 0;
         mesh[vertexIndex + 4] = 0;
       };
       };
       if (vertex.normalIndex !== -1) {
       if (vertex.normalIndex !== -1) {
-        normal = normals[vertex.normalIndex];
-        mesh[vertexIndex + 5] = normal[0];
-        mesh[vertexIndex + 6] = normal[1];
-        mesh[vertexIndex + 7] = normal[2];
+        normal = new pas.GLTypes.TVec3(rtl.getObject(normals[vertex.normalIndex]));
+        mesh[vertexIndex + 5] = normal.x;
+        mesh[vertexIndex + 6] = normal.y;
+        mesh[vertexIndex + 7] = normal.z;
       };
       };
     };
     };
-    objData.verticies = mesh;
-    objData.indices = new Uint16Array(indices);
-    Result = new $mod.TOBJData(objData);
+    data.verticies = mesh;
+    data.indicies = new Uint16Array(indices);
+    Result = new $mod.TModelData(data);
     return Result;
     return Result;
   };
   };
   this.GLSizeof = function (glType) {
   this.GLSizeof = function (glType) {
@@ -2495,16 +2530,16 @@ rtl.module("GLUtils",["System","Mat4","GLTypes","Types","browserconsole","Web","
   };
   };
   $impl.TOBJVertex = function (s) {
   $impl.TOBJVertex = function (s) {
     if (s) {
     if (s) {
-      this.position = s.position;
+      this.position = new pas.GLTypes.TVec3(s.position);
       this.textureIndex = s.textureIndex;
       this.textureIndex = s.textureIndex;
       this.normalIndex = s.normalIndex;
       this.normalIndex = s.normalIndex;
     } else {
     } else {
-      this.position = [];
+      this.position = new pas.GLTypes.TVec3();
       this.textureIndex = 0;
       this.textureIndex = 0;
       this.normalIndex = 0;
       this.normalIndex = 0;
     };
     };
     this.$equal = function (b) {
     this.$equal = function (b) {
-      return (this.position === b.position) && ((this.textureIndex === b.textureIndex) && (this.normalIndex === b.normalIndex));
+      return this.position.$equal(b.position) && ((this.textureIndex === b.textureIndex) && (this.normalIndex === b.normalIndex));
     };
     };
   };
   };
   $impl.ProcessFace = function (verticies, indices, face) {
   $impl.ProcessFace = function (verticies, indices, face) {
@@ -2579,10 +2614,10 @@ rtl.module("program",["System","Types","Mat4","GLUtils","GLTypes","SysUtils","br
       this.request.send();
       this.request.send();
     };
     };
     this.HandleLoaded = function () {
     this.HandleLoaded = function () {
-      var data = new pas.GLUtils.TOBJData();
+      var data = new pas.GLUtils.TModelData();
       if (((this.request.readyState === 4) && (this.request.status === 200)) && (this.request.responseText.length > 0)) {
       if (((this.request.readyState === 4) && (this.request.status === 200)) && (this.request.responseText.length > 0)) {
-        data = new pas.GLUtils.TOBJData(pas.GLUtils.LoadOBJFile(this.request.responseText));
-        $mod.dragonModel = pas.GLUtils.TModel.$create("Create$1",[this.gl$1,new pas.GLUtils.TOBJData(data)]);
+        data = new pas.GLUtils.TModelData(pas.GLUtils.LoadOBJFile(this.request.responseText));
+        $mod.dragonModel = pas.GLUtils.TModel.$create("Create$1",[this.gl$1,new pas.GLUtils.TModelData(data)]);
         $mod.StartAnimatingCanvas();
         $mod.StartAnimatingCanvas();
       };
       };
     };
     };
@@ -2622,8 +2657,8 @@ rtl.module("program",["System","Types","Mat4","GLUtils","GLTypes","SysUtils","br
     $mod.viewTransform = $mod.viewTransform.Multiply(pas.Mat4.TMat4.$create("Translate",[0,-3,-20]));
     $mod.viewTransform = $mod.viewTransform.Multiply(pas.Mat4.TMat4.$create("Translate",[0,-3,-20]));
     $mod.shader.SetUniformMat4("viewTransform",$mod.viewTransform);
     $mod.shader.SetUniformMat4("viewTransform",$mod.viewTransform);
     $mod.shader.SetUniformMat4("inverseViewTransform",$mod.viewTransform.Inverse());
     $mod.shader.SetUniformMat4("inverseViewTransform",$mod.viewTransform.Inverse());
-    $mod.shader.SetUniformVec3("lightPosition",pas.GLTypes.V3(0,0,25));
-    $mod.shader.SetUniformVec3("lightColor",pas.GLTypes.V3(1,1,1));
+    $mod.shader.SetUniformVec3("lightPosition",new pas.GLTypes.TVec3(pas.GLTypes.V3(0,0,25)));
+    $mod.shader.SetUniformVec3("lightColor",new pas.GLTypes.TVec3(pas.GLTypes.V3(1,1,1)));
     $mod.shader.SetUniformFloat("shineDamper",10);
     $mod.shader.SetUniformFloat("shineDamper",10);
     $mod.shader.SetUniformFloat("reflectivity",1);
     $mod.shader.SetUniformFloat("reflectivity",1);
     $mod.TModelLoader.$create("Create$1",[$mod.gl,"res\/dragon.obj"]);
     $mod.TModelLoader.$create("Create$1",[$mod.gl,"res\/dragon.obj"]);

+ 1 - 1
Pas2JS_WebGL_OBJ.pas

@@ -64,7 +64,7 @@ type
 
 
 procedure TModelLoader.HandleLoaded;
 procedure TModelLoader.HandleLoaded;
 var
 var
-	data: TOBJData;
+	data: TModelData;
 begin
 begin
 	if (request.readyState = 4) and (request.status = 200) and (length(request.responseText) > 0) then
 	if (request.readyState = 4) and (request.status = 200) and (length(request.responseText) > 0) then
 		begin
 		begin

+ 2245 - 0
Pas2JS_WebGL_Terrain.js

@@ -0,0 +1,2245 @@
+var pas = {};
+
+var rtl = {
+
+  quiet: false,
+  debug_load_units: false,
+  debug_rtti: false,
+
+  debug: function(){
+    if (rtl.quiet || !console || !console.log) return;
+    console.log(arguments);
+  },
+
+  error: function(s){
+    rtl.debug('Error: ',s);
+    throw s;
+  },
+
+  warn: function(s){
+    rtl.debug('Warn: ',s);
+  },
+
+  hasString: function(s){
+    return rtl.isString(s) && (s.length>0);
+  },
+
+  isArray: function(a) {
+    return Array.isArray(a);
+  },
+
+  isFunction: function(f){
+    return typeof(f)==="function";
+  },
+
+  isModule: function(m){
+    return rtl.isObject(m) && rtl.hasString(m.$name) && (pas[m.$name]===m);
+  },
+
+  isImplementation: function(m){
+    return rtl.isObject(m) && rtl.isModule(m.$module) && (m.$module.$impl===m);
+  },
+
+  isNumber: function(n){
+    return typeof(n)==="number";
+  },
+
+  isObject: function(o){
+    var s=typeof(o);
+    return (typeof(o)==="object") && (o!=null);
+  },
+
+  isString: function(s){
+    return typeof(s)==="string";
+  },
+
+  getNumber: function(n){
+    return typeof(n)==="number"?n:NaN;
+  },
+
+  getChar: function(c){
+    return ((typeof(c)==="string") && (c.length===1)) ? c : "";
+  },
+
+  getObject: function(o){
+    return ((typeof(o)==="object") || (typeof(o)==='function')) ? o : null;
+  },
+
+  isPasClass: function(type){
+    return (rtl.isObject(type) && type.hasOwnProperty('$classname') && rtl.isObject(type.$module));
+  },
+
+  isPasClassInstance: function(type){
+    return (rtl.isObject(type) && rtl.isPasClass(type.$class));
+  },
+
+  hexStr: function(n,digits){
+    return ("000000000000000"+n.toString(16).toUpperCase()).slice(-digits);
+  },
+
+  m_loading: 0,
+  m_loading_intf: 1,
+  m_intf_loaded: 2,
+  m_loading_impl: 3, // loading all used unit
+  m_initializing: 4, // running initialization
+  m_initialized: 5,
+
+  module: function(module_name, intfuseslist, intfcode, impluseslist, implcode){
+    if (rtl.debug_load_units) rtl.debug('rtl.module name="'+module_name+'" intfuses='+intfuseslist+' impluses='+impluseslist+' hasimplcode='+rtl.isFunction(implcode));
+    if (!rtl.hasString(module_name)) rtl.error('invalid module name "'+module_name+'"');
+    if (!rtl.isArray(intfuseslist)) rtl.error('invalid interface useslist of "'+module_name+'"');
+    if (!rtl.isFunction(intfcode)) rtl.error('invalid interface code of "'+module_name+'"');
+    if (!(impluseslist==undefined) && !rtl.isArray(impluseslist)) rtl.error('invalid implementation useslist of "'+module_name+'"');
+    if (!(implcode==undefined) && !rtl.isFunction(implcode)) rtl.error('invalid implementation code of "'+module_name+'"');
+
+    if (pas[module_name])
+      rtl.error('module "'+module_name+'" is already registered');
+
+    var module = pas[module_name] = {
+      $name: module_name,
+      $intfuseslist: intfuseslist,
+      $impluseslist: impluseslist,
+      $state: rtl.m_loading,
+      $intfcode: intfcode,
+      $implcode: implcode,
+      $impl: null,
+      $rtti: Object.create(rtl.tSectionRTTI)
+    };
+    module.$rtti.$module = module;
+    if (implcode) module.$impl = {
+      $module: module,
+      $rtti: module.$rtti
+    };
+  },
+
+  exitcode: 0,
+
+  run: function(module_name){
+  
+    function doRun(){
+      if (!rtl.hasString(module_name)) module_name='program';
+      if (rtl.debug_load_units) rtl.debug('rtl.run module="'+module_name+'"');
+      rtl.initRTTI();
+      var module = pas[module_name];
+      if (!module) rtl.error('rtl.run module "'+module_name+'" missing');
+      rtl.loadintf(module);
+      rtl.loadimpl(module);
+      if (module_name=='program'){
+        if (rtl.debug_load_units) rtl.debug('running $main');
+        var r = pas.program.$main();
+        if (rtl.isNumber(r)) rtl.exitcode = r;
+      }
+    }
+    
+    if (rtl.showUncaughtExceptions) {
+      try{
+        doRun();
+      } catch(re) {
+        var errMsg = re.hasOwnProperty('$class') ? re.$class.$classname : '';
+	    errMsg +=  ((errMsg) ? ': ' : '') + (re.hasOwnProperty('fMessage') ? re.fMessage : re);
+        alert('Uncaught Exception : '+errMsg);
+        rtl.exitCode = 216;
+      }
+    } else {
+      doRun();
+    }
+    return rtl.exitcode;
+  },
+
+  loadintf: function(module){
+    if (module.$state>rtl.m_loading_intf) return; // already finished
+    if (rtl.debug_load_units) rtl.debug('loadintf: "'+module.$name+'"');
+    if (module.$state===rtl.m_loading_intf)
+      rtl.error('unit cycle detected "'+module.$name+'"');
+    module.$state=rtl.m_loading_intf;
+    // load interfaces of interface useslist
+    rtl.loaduseslist(module,module.$intfuseslist,rtl.loadintf);
+    // run interface
+    if (rtl.debug_load_units) rtl.debug('loadintf: run intf of "'+module.$name+'"');
+    module.$intfcode(module.$intfuseslist);
+    // success
+    module.$state=rtl.m_intf_loaded;
+    // Note: units only used in implementations are not yet loaded (not even their interfaces)
+  },
+
+  loaduseslist: function(module,useslist,f){
+    if (useslist==undefined) return;
+    for (var i in useslist){
+      var unitname=useslist[i];
+      if (rtl.debug_load_units) rtl.debug('loaduseslist of "'+module.$name+'" uses="'+unitname+'"');
+      if (pas[unitname]==undefined)
+        rtl.error('module "'+module.$name+'" misses "'+unitname+'"');
+      f(pas[unitname]);
+    }
+  },
+
+  loadimpl: function(module){
+    if (module.$state>=rtl.m_loading_impl) return; // already processing
+    if (module.$state<rtl.m_intf_loaded) rtl.error('loadimpl: interface not loaded of "'+module.$name+'"');
+    if (rtl.debug_load_units) rtl.debug('loadimpl: load uses of "'+module.$name+'"');
+    module.$state=rtl.m_loading_impl;
+    // load interfaces of implementation useslist
+    rtl.loaduseslist(module,module.$impluseslist,rtl.loadintf);
+    // load implementation of interfaces useslist
+    rtl.loaduseslist(module,module.$intfuseslist,rtl.loadimpl);
+    // load implementation of implementation useslist
+    rtl.loaduseslist(module,module.$impluseslist,rtl.loadimpl);
+    // Note: At this point all interfaces used by this unit are loaded. If
+    //   there are implementation uses cycles some used units might not yet be
+    //   initialized. This is by design.
+    // run implementation
+    if (rtl.debug_load_units) rtl.debug('loadimpl: run impl of "'+module.$name+'"');
+    if (rtl.isFunction(module.$implcode)) module.$implcode(module.$impluseslist);
+    // run initialization
+    if (rtl.debug_load_units) rtl.debug('loadimpl: run init of "'+module.$name+'"');
+    module.$state=rtl.m_initializing;
+    if (rtl.isFunction(module.$init)) module.$init();
+    // unit initialized
+    module.$state=rtl.m_initialized;
+  },
+
+  createCallback: function(scope, fn){
+    var cb;
+    if (typeof(fn)==='string'){
+      cb = function(){
+        return scope[fn].apply(scope,arguments);
+      };
+    } else {
+      cb = function(){
+        return fn.apply(scope,arguments);
+      };
+    };
+    cb.scope = scope;
+    cb.fn = fn;
+    return cb;
+  },
+
+  cloneCallback: function(cb){
+    return rtl.createCallback(cb.scope,cb.fn);
+  },
+
+  eqCallback: function(a,b){
+    // can be a function or a function wrapper
+    if (a==b){
+      return true;
+    } else {
+      return (a!=null) && (b!=null) && (a.fn) && (a.scope===b.scope) && (a.fn==b.fn);
+    }
+  },
+
+  initClass: function(c,parent,name,initfn){
+    parent[name] = c;
+    c.$classname = name;
+    if ((parent.$module) && (parent.$module.$impl===parent)) parent=parent.$module;
+    c.$parent = parent;
+    c.$fullname = parent.$name+'.'+name;
+    if (rtl.isModule(parent)){
+      c.$module = parent;
+      c.$name = name;
+    } else {
+      c.$module = parent.$module;
+      c.$name = parent.name+'.'+name;
+    };
+    // rtti
+    if (rtl.debug_rtti) rtl.debug('initClass '+c.$fullname);
+    var t = c.$module.$rtti.$Class(c.$name,{ "class": c, module: parent });
+    c.$rtti = t;
+    if (rtl.isObject(c.$ancestor)) t.ancestor = c.$ancestor.$rtti;
+    if (!t.ancestor) t.ancestor = null;
+    // init members
+    initfn.call(c);
+  },
+
+  createClass: function(parent,name,ancestor,initfn){
+    // create a normal class,
+    // ancestor must be null or a normal class,
+    // the root ancestor can be an external class
+    var c = null;
+    if (ancestor != null){
+      c = Object.create(ancestor);
+      c.$ancestor = ancestor;
+      // Note:
+      // if root is an "object" then c.$ancestor === Object.getPrototypeOf(c)
+      // if root is a "function" then c.$ancestor === c.__proto__, Object.getPrototypeOf(c) returns the root
+    } else {
+      c = {};
+      c.$create = function(fnname,args){
+        if (args == undefined) args = [];
+        var o = Object.create(this);
+        o.$class = this; // Note: o.$class === Object.getPrototypeOf(o)
+        o.$init();
+        try{
+          o[fnname].apply(o,args);
+          o.AfterConstruction();
+        } catch($e){
+          o.$destroy;
+          throw $e;
+        }
+        return o;
+      };
+      c.$destroy = function(fnname){
+        this.BeforeDestruction();
+        this[fnname]();
+        this.$final;
+      };
+    };
+    rtl.initClass(c,parent,name,initfn);
+  },
+
+  createClassExt: function(parent,name,ancestor,newinstancefnname,initfn){
+    // Create a class using an external ancestor.
+    // If newinstancefnname is given, use that function to create the new object.
+    // If exist call BeforeDestruction and AfterConstruction.
+    var c = null;
+    c = Object.create(ancestor);
+    c.$create = function(fnname,args){
+      if (args == undefined) args = [];
+      var o = null;
+      if (newinstancefnname.length>0){
+        o = this[newinstancefnname](fnname,args);
+      } else {
+        o = Object.create(this);
+      }
+      o.$class = this; // Note: o.$class === Object.getPrototypeOf(o)
+      o.$init();
+      try{
+        o[fnname].apply(o,args);
+        if (o.AfterConstruction) o.AfterConstruction();
+      } catch($e){
+        o.$destroy;
+        throw $e;
+      }
+      return o;
+    };
+    c.$destroy = function(fnname){
+      if (this.BeforeDestruction) this.BeforeDestruction();
+      this[fnname]();
+      this.$final;
+    };
+    rtl.initClass(c,parent,name,initfn);
+  },
+
+  tObjectDestroy: "Destroy",
+
+  free: function(obj,name){
+    if (obj[name]==null) return;
+    obj[name].$destroy(rtl.tObjectDestroy);
+    obj[name]=null;
+  },
+
+  freeLoc: function(obj){
+    if (obj==null) return;
+    obj.$destroy(rtl.tObjectDestroy);
+    return null;
+  },
+
+  is: function(instance,type){
+    return type.isPrototypeOf(instance) || (instance===type);
+  },
+
+  isExt: function(instance,type,mode){
+    // mode===1 means instance must be a Pascal class instance
+    // mode===2 means instance must be a Pascal class
+    // Notes:
+    // isPrototypeOf and instanceof return false on equal
+    // isPrototypeOf does not work for Date.isPrototypeOf(new Date())
+    //   so if isPrototypeOf is false test with instanceof
+    // instanceof needs a function on right side
+    if (instance == null) return false; // Note: ==null checks for undefined too
+    if ((typeof(type) !== 'object') && (typeof(type) !== 'function')) return false;
+    if (instance === type){
+      if (mode===1) return false;
+      if (mode===2) return rtl.isPasClass(instance);
+      return true;
+    }
+    if (type.isPrototypeOf && type.isPrototypeOf(instance)){
+      if (mode===1) return rtl.isPasClassInstance(instance);
+      if (mode===2) return rtl.isPasClass(instance);
+      return true;
+    }
+    if ((typeof type == 'function') && (instance instanceof type)) return true;
+    return false;
+  },
+
+  Exception: null,
+  EInvalidCast: null,
+  EAbstractError: null,
+  ERangeError: null,
+
+  raiseE: function(typename){
+    var t = rtl[typename];
+    if (t==null){
+      var mod = pas.SysUtils;
+      if (!mod) mod = pas.sysutils;
+      if (mod){
+        t = mod[typename];
+        if (!t) t = mod[typename.toLowerCase()];
+        if (!t) t = mod['Exception'];
+        if (!t) t = mod['exception'];
+      }
+    }
+    if (t){
+      if (t.Create){
+        throw t.$create("Create");
+      } else if (t.create){
+        throw t.$create("create");
+      }
+    }
+    if (typename === "EInvalidCast") throw "invalid type cast";
+    if (typename === "EAbstractError") throw "Abstract method called";
+    if (typename === "ERangeError") throw "range error";
+    throw typename;
+  },
+
+  as: function(instance,type){
+    if((instance === null) || rtl.is(instance,type)) return instance;
+    rtl.raiseE("EInvalidCast");
+  },
+
+  asExt: function(instance,type,mode){
+    if((instance === null) || rtl.isExt(instance,type,mode)) return instance;
+    rtl.raiseE("EInvalidCast");
+  },
+
+  createInterface: function(module, name, guid, fnnames, ancestor, initfn){
+    //console.log('createInterface name="'+name+'" guid="'+guid+'" names='+fnnames);
+    var i = ancestor?Object.create(ancestor):{};
+    module[name] = i;
+    i.$module = module;
+    i.$name = name;
+    i.$fullname = module.$name+'.'+name;
+    i.$guid = guid;
+    i.$guidr = null;
+    i.$names = fnnames?fnnames:[];
+    if (rtl.isFunction(initfn)){
+      // rtti
+      if (rtl.debug_rtti) rtl.debug('createInterface '+i.$fullname);
+      var t = i.$module.$rtti.$Interface(name,{ "interface": i, module: module });
+      i.$rtti = t;
+      if (ancestor) t.ancestor = ancestor.$rtti;
+      if (!t.ancestor) t.ancestor = null;
+      initfn.call(i);
+    }
+    return i;
+  },
+
+  strToGUIDR: function(s,g){
+    var p = 0;
+    function n(l){
+      var h = s.substr(p,l);
+      p+=l;
+      return parseInt(h,16);
+    }
+    p+=1; // skip {
+    g.D1 = n(8);
+    p+=1; // skip -
+    g.D2 = n(4);
+    p+=1; // skip -
+    g.D3 = n(4);
+    p+=1; // skip -
+    if (!g.D4) g.D4=[];
+    g.D4[0] = n(2);
+    g.D4[1] = n(2);
+    p+=1; // skip -
+    for(var i=2; i<8; i++) g.D4[i] = n(2);
+    return g;
+  },
+
+  guidrToStr: function(g){
+    if (g.$intf) return g.$intf.$guid;
+    var h = rtl.hexStr;
+    var s='{'+h(g.D1,8)+'-'+h(g.D2,4)+'-'+h(g.D3,4)+'-'+h(g.D4[0],2)+h(g.D4[1],2)+'-';
+    for (var i=2; i<8; i++) s+=h(g.D4[i],2);
+    s+='}';
+    return s;
+  },
+
+  createTGUID: function(guid){
+    var TGuid = (pas.System)?pas.System.TGuid:pas.system.tguid;
+    var g = rtl.strToGUIDR(guid,new TGuid());
+    return g;
+  },
+
+  getIntfGUIDR: function(intfTypeOrVar){
+    if (!intfTypeOrVar) return null;
+    if (!intfTypeOrVar.$guidr){
+      var g = rtl.createTGUID(intfTypeOrVar.$guid);
+      if (!intfTypeOrVar.hasOwnProperty('$guid')) intfTypeOrVar = Object.getPrototypeOf(intfTypeOrVar);
+      g.$intf = intfTypeOrVar;
+      intfTypeOrVar.$guidr = g;
+    }
+    return intfTypeOrVar.$guidr;
+  },
+
+  addIntf: function (aclass, intf, map){
+    function jmp(fn){
+      if (typeof(fn)==="function"){
+        return function(){ return fn.apply(this.$o,arguments); };
+      } else {
+        return function(){ rtl.raiseE('EAbstractError'); };
+      }
+    }
+    if(!map) map = {};
+    var t = intf;
+    var item = Object.create(t);
+    aclass.$intfmaps[intf.$guid] = item;
+    do{
+      var names = t.$names;
+      if (!names) break;
+      for (var i=0; i<names.length; i++){
+        var intfname = names[i];
+        var fnname = map[intfname];
+        if (!fnname) fnname = intfname;
+        //console.log('addIntf: intftype='+t.$name+' index='+i+' intfname="'+intfname+'" fnname="'+fnname+'" proc='+typeof(fn));
+        item[intfname] = jmp(aclass[fnname]);
+      }
+      t = Object.getPrototypeOf(t);
+    }while(t!=null);
+  },
+
+  getIntfG: function (obj, guid, query){
+    if (!obj) return null;
+    //console.log('getIntfG: obj='+obj.$classname+' guid='+guid+' query='+query);
+    // search
+    var maps = obj.$intfmaps;
+    if (!maps) return null;
+    var item = maps[guid];
+    if (!item) return null;
+    // check delegation
+    //console.log('getIntfG: obj='+obj.$classname+' guid='+guid+' query='+query+' item='+typeof(item));
+    if (typeof item === 'function') return item.call(obj); // COM: contains _AddRef
+    // check cache
+    var intf = null;
+    if (obj.$interfaces){
+      intf = obj.$interfaces[guid];
+      //console.log('getIntfG: obj='+obj.$classname+' guid='+guid+' cache='+typeof(intf));
+    }
+    if (!intf){ // intf can be undefined!
+      intf = Object.create(item);
+      intf.$o = obj;
+      if (!obj.$interfaces) obj.$interfaces = {};
+      obj.$interfaces[guid] = intf;
+    }
+    if (typeof(query)==='object'){
+      // called by queryIntfT
+      var o = null;
+      if (intf.QueryInterface(rtl.getIntfGUIDR(query),
+          {get:function(){ return o; }, set:function(v){ o=v; }}) === 0){
+        return o;
+      } else {
+        return null;
+      }
+    } else if(query===2){
+      // called by TObject.GetInterfaceByStr
+      if (intf.$kind === 'com') intf._AddRef();
+    }
+    return intf;
+  },
+
+  getIntfT: function(obj,intftype){
+    return rtl.getIntfG(obj,intftype.$guid);
+  },
+
+  queryIntfT: function(obj,intftype){
+    return rtl.getIntfG(obj,intftype.$guid,intftype);
+  },
+
+  queryIntfIsT: function(obj,intftype){
+    var i = rtl.queryIntfG(obj,intftype.$guid);
+    if (!i) return false;
+    if (i.$kind === 'com') i._Release();
+    return true;
+  },
+
+  asIntfT: function (obj,intftype){
+    var i = rtl.getIntfG(obj,intftype.$guid);
+    if (i!==null) return i;
+    rtl.raiseEInvalidCast();
+  },
+
+  intfIsClass: function(intf,classtype){
+    return (intf!=null) && (rtl.is(intf.$o,classtype));
+  },
+
+  intfAsClass: function(intf,classtype){
+    if (intf==null) return null;
+    return rtl.as(intf.$o,classtype);
+  },
+
+  intfToClass: function(intf,classtype){
+    if ((intf!==null) && rtl.is(intf.$o,classtype)) return intf.$o;
+    return null;
+  },
+
+  // interface reference counting
+  intfRefs: { // base object for temporary interface variables
+    ref: function(id,intf){
+      // called for temporary interface references needing delayed release
+      var old = this[id];
+      //console.log('rtl.intfRefs.ref: id='+id+' old="'+(old?old.$name:'null')+'" intf="'+(intf?intf.$name:'null'));
+      if (old){
+        // called again, e.g. in a loop
+        delete this[id];
+        old._Release(); // may fail
+      }
+      this[id]=intf;
+      return intf;
+    },
+    free: function(){
+      //console.log('rtl.intfRefs.free...');
+      for (var id in this){
+        if (this.hasOwnProperty(id)) this[id]._Release;
+      }
+    }
+  },
+
+  createIntfRefs: function(){
+    //console.log('rtl.createIntfRefs');
+    return Object.create(rtl.intfRefs);
+  },
+
+  setIntfP: function(path,name,value,skipAddRef){
+    var old = path[name];
+    //console.log('rtl.setIntfP path='+path+' name='+name+' old="'+(old?old.$name:'null')+'" value="'+(value?value.$name:'null')+'"');
+    if (old === value) return;
+    if (old !== null){
+      path[name]=null;
+      old._Release();
+    }
+    if (value !== null){
+      if (!skipAddRef) value._AddRef();
+      path[name]=value;
+    }
+  },
+
+  setIntfL: function(old,value,skipAddRef){
+    //console.log('rtl.setIntfL old="'+(old?old.$name:'null')+'" value="'+(value?value.$name:'null')+'"');
+    if (old !== value){
+      if (value!==null){
+        if (!skipAddRef) value._AddRef();
+      }
+      if (old!==null){
+        old._Release();  // Release after AddRef, to avoid double Release if Release creates an exception
+      }
+    } else if (skipAddRef){
+      if (old!==null){
+        old._Release();  // value has an AddRef
+      }
+    }
+    return value;
+  },
+
+  _AddRef: function(intf){
+    //if (intf) console.log('rtl._AddRef intf="'+(intf?intf.$name:'null')+'"');
+    if (intf) intf._AddRef();
+    return intf;
+  },
+
+  _Release: function(intf){
+    //if (intf) console.log('rtl._Release intf="'+(intf?intf.$name:'null')+'"');
+    if (intf) intf._Release();
+    return intf;
+  },
+
+  checkMethodCall: function(obj,type){
+    if (rtl.isObject(obj) && rtl.is(obj,type)) return;
+    rtl.raiseE("EInvalidCast");
+  },
+
+  rc: function(i,minval,maxval){
+    // range check integer
+    if ((Math.floor(i)===i) && (i>=minval) && (i<=maxval)) return i;
+    rtl.raiseE('ERangeError');
+  },
+
+  rcc: function(c,minval,maxval){
+    // range check char
+    if ((typeof(c)==='string') && (c.length===1)){
+      var i = c.charCodeAt(0);
+      if ((i>=minval) && (i<=maxval)) return c;
+    }
+    rtl.raiseE('ERangeError');
+  },
+
+  rcSetCharAt: function(s,index,c){
+    // range check setCharAt
+    if ((typeof(s)!=='string') || (index<0) || (index>=s.length)) rtl.raiseE('ERangeError');
+    return rtl.setCharAt(s,index,c);
+  },
+
+  rcCharAt: function(s,index){
+    // range check charAt
+    if ((typeof(s)!=='string') || (index<0) || (index>=s.length)) rtl.raiseE('ERangeError');
+    return s.charAt(index);
+  },
+
+  rcArrR: function(arr,index){
+    // range check read array
+    if (Array.isArray(arr) && (typeof(index)==='number') && (index>=0) && (index<arr.length)){
+      if (arguments.length>2){
+        // arr,index1,index2,...
+        arr=arr[index];
+        for (var i=2; i<arguments.length; i++) arr=rtl.rcArrR(arr,arguments[i]);
+        return arr;
+      }
+      return arr[index];
+    }
+    rtl.raiseE('ERangeError');
+  },
+
+  rcArrW: function(arr,index,value){
+    // range check write array
+    // arr,index1,index2,...,value
+    for (var i=3; i<arguments.length; i++){
+      arr=rtl.rcArrR(arr,index);
+      index=arguments[i-1];
+      value=arguments[i];
+    }
+    if (Array.isArray(arr) && (typeof(index)==='number') && (index>=0) && (index<arr.length)){
+      return arr[index]=value;
+    }
+    rtl.raiseE('ERangeError');
+  },
+
+  length: function(arr){
+    return (arr == null) ? 0 : arr.length;
+  },
+
+  arraySetLength: function(arr,defaultvalue,newlength){
+    // multi dim: (arr,defaultvalue,dim1,dim2,...)
+    if (arr == null) arr = [];
+    var p = arguments;
+    function setLength(a,argNo){
+      var oldlen = a.length;
+      var newlen = p[argNo];
+      if (oldlen!==newlength){
+        a.length = newlength;
+        if (argNo === p.length-1){
+          if (rtl.isArray(defaultvalue)){
+            for (var i=oldlen; i<newlen; i++) a[i]=[]; // nested array
+          } else if (rtl.isFunction(defaultvalue)){
+            for (var i=oldlen; i<newlen; i++) a[i]=new defaultvalue(); // e.g. record
+          } else if (rtl.isObject(defaultvalue)) {
+            for (var i=oldlen; i<newlen; i++) a[i]={}; // e.g. set
+          } else {
+            for (var i=oldlen; i<newlen; i++) a[i]=defaultvalue;
+          }
+        } else {
+          for (var i=oldlen; i<newlen; i++) a[i]=[]; // nested array
+        }
+      }
+      if (argNo < p.length-1){
+        // multi argNo
+        for (var i=0; i<newlen; i++) a[i]=setLength(a[i],argNo+1);
+      }
+      return a;
+    }
+    return setLength(arr,2);
+  },
+
+  arrayEq: function(a,b){
+    if (a===null) return b===null;
+    if (b===null) return false;
+    if (a.length!==b.length) return false;
+    for (var i=0; i<a.length; i++) if (a[i]!==b[i]) return false;
+    return true;
+  },
+
+  arrayClone: function(type,src,srcpos,end,dst,dstpos){
+    // type: 0 for references, "refset" for calling refSet(), a function for new type()
+    // src must not be null
+    // This function does not range check.
+    if (rtl.isFunction(type)){
+      for (; srcpos<end; srcpos++) dst[dstpos++] = new type(src[srcpos]); // clone record
+    } else if((typeof(type)==="string") && (type === 'refSet')) {
+      for (; srcpos<end; srcpos++) dst[dstpos++] = rtl.refSet(src[srcpos]); // ref set
+    }  else {
+      for (; srcpos<end; srcpos++) dst[dstpos++] = src[srcpos]; // reference
+    };
+  },
+
+  arrayConcat: function(type){
+    // type: see rtl.arrayClone
+    var a = [];
+    var l = 0;
+    for (var i=1; i<arguments.length; i++) l+=arguments[i].length;
+    a.length = l;
+    l=0;
+    for (var i=1; i<arguments.length; i++){
+      var src = arguments[i];
+      if (src == null) continue;
+      rtl.arrayClone(type,src,0,src.length,a,l);
+      l+=src.length;
+    };
+    return a;
+  },
+
+  arrayCopy: function(type, srcarray, index, count){
+    // type: see rtl.arrayClone
+    // if count is missing, use srcarray.length
+    if (srcarray == null) return [];
+    if (index < 0) index = 0;
+    if (count === undefined) count=srcarray.length;
+    var end = index+count;
+    if (end>srcarray.length) end = srcarray.length;
+    if (index>=end) return [];
+    if (type===0){
+      return srcarray.slice(index,end);
+    } else {
+      var a = [];
+      a.length = end-index;
+      rtl.arrayClone(type,srcarray,index,end,a,0);
+      return a;
+    }
+  },
+
+  setCharAt: function(s,index,c){
+    return s.substr(0,index)+c+s.substr(index+1);
+  },
+
+  getResStr: function(mod,name){
+    var rs = mod.$resourcestrings[name];
+    return rs.current?rs.current:rs.org;
+  },
+
+  createSet: function(){
+    var s = {};
+    for (var i=0; i<arguments.length; i++){
+      if (arguments[i]!=null){
+        s[arguments[i]]=true;
+      } else {
+        var first=arguments[i+=1];
+        var last=arguments[i+=1];
+        for(var j=first; j<=last; j++) s[j]=true;
+      }
+    }
+    return s;
+  },
+
+  cloneSet: function(s){
+    var r = {};
+    for (var key in s) r[key]=true;
+    return r;
+  },
+
+  refSet: function(s){
+    s.$shared = true;
+    return s;
+  },
+
+  includeSet: function(s,enumvalue){
+    if (s.$shared) s = rtl.cloneSet(s);
+    s[enumvalue] = true;
+    return s;
+  },
+
+  excludeSet: function(s,enumvalue){
+    if (s.$shared) s = rtl.cloneSet(s);
+    delete s[enumvalue];
+    return s;
+  },
+
+  diffSet: function(s,t){
+    var r = {};
+    for (var key in s) if (!t[key]) r[key]=true;
+    delete r.$shared;
+    return r;
+  },
+
+  unionSet: function(s,t){
+    var r = {};
+    for (var key in s) r[key]=true;
+    for (var key in t) r[key]=true;
+    delete r.$shared;
+    return r;
+  },
+
+  intersectSet: function(s,t){
+    var r = {};
+    for (var key in s) if (t[key]) r[key]=true;
+    delete r.$shared;
+    return r;
+  },
+
+  symDiffSet: function(s,t){
+    var r = {};
+    for (var key in s) if (!t[key]) r[key]=true;
+    for (var key in t) if (!s[key]) r[key]=true;
+    delete r.$shared;
+    return r;
+  },
+
+  eqSet: function(s,t){
+    for (var key in s) if (!t[key] && (key!='$shared')) return false;
+    for (var key in t) if (!s[key] && (key!='$shared')) return false;
+    return true;
+  },
+
+  neSet: function(s,t){
+    return !rtl.eqSet(s,t);
+  },
+
+  leSet: function(s,t){
+    for (var key in s) if (!t[key] && (key!='$shared')) return false;
+    return true;
+  },
+
+  geSet: function(s,t){
+    for (var key in t) if (!s[key] && (key!='$shared')) return false;
+    return true;
+  },
+
+  strSetLength: function(s,newlen){
+    var oldlen = s.length;
+    if (oldlen > newlen){
+      return s.substring(0,newlen);
+    } else if (s.repeat){
+      // Note: repeat needs ECMAScript6!
+      return s+' '.repeat(newlen-oldlen);
+    } else {
+       while (oldlen<newlen){
+         s+=' ';
+         oldlen++;
+       };
+       return s;
+    }
+  },
+
+  spaceLeft: function(s,width){
+    var l=s.length;
+    if (l>=width) return s;
+    if (s.repeat){
+      // Note: repeat needs ECMAScript6!
+      return ' '.repeat(width-l) + s;
+    } else {
+      while (l<width){
+        s=' '+s;
+        l++;
+      };
+    };
+  },
+
+  floatToStr : function(d,w,p){
+    // input 1-3 arguments: double, width, precision
+    if (arguments.length>2){
+      return rtl.spaceLeft(d.toFixed(p),w);
+    } else {
+	  // exponent width
+	  var pad = "";
+	  var ad = Math.abs(d);
+	  if (ad<1.0e+10) {
+		pad='00';
+	  } else if (ad<1.0e+100) {
+		pad='0';
+      }  	
+	  if (arguments.length<2) {
+	    w=9;		
+      } else if (w<9) {
+		w=9;
+      }		  
+      var p = w-8;
+      var s=(d>0 ? " " : "" ) + d.toExponential(p);
+      s=s.replace(/e(.)/,'E$1'+pad);
+      return rtl.spaceLeft(s,w);
+    }
+  },
+
+  initRTTI: function(){
+    if (rtl.debug_rtti) rtl.debug('initRTTI');
+
+    // base types
+    rtl.tTypeInfo = { name: "tTypeInfo" };
+    function newBaseTI(name,kind,ancestor){
+      if (!ancestor) ancestor = rtl.tTypeInfo;
+      if (rtl.debug_rtti) rtl.debug('initRTTI.newBaseTI "'+name+'" '+kind+' ("'+ancestor.name+'")');
+      var t = Object.create(ancestor);
+      t.name = name;
+      t.kind = kind;
+      rtl[name] = t;
+      return t;
+    };
+    function newBaseInt(name,minvalue,maxvalue,ordtype){
+      var t = newBaseTI(name,1 /* tkInteger */,rtl.tTypeInfoInteger);
+      t.minvalue = minvalue;
+      t.maxvalue = maxvalue;
+      t.ordtype = ordtype;
+      return t;
+    };
+    newBaseTI("tTypeInfoInteger",1 /* tkInteger */);
+    newBaseInt("shortint",-0x80,0x7f,0);
+    newBaseInt("byte",0,0xff,1);
+    newBaseInt("smallint",-0x8000,0x7fff,2);
+    newBaseInt("word",0,0xffff,3);
+    newBaseInt("longint",-0x80000000,0x7fffffff,4);
+    newBaseInt("longword",0,0xffffffff,5);
+    newBaseInt("nativeint",-0x10000000000000,0xfffffffffffff,6);
+    newBaseInt("nativeuint",0,0xfffffffffffff,7);
+    newBaseTI("char",2 /* tkChar */);
+    newBaseTI("string",3 /* tkString */);
+    newBaseTI("tTypeInfoEnum",4 /* tkEnumeration */,rtl.tTypeInfoInteger);
+    newBaseTI("tTypeInfoSet",5 /* tkSet */);
+    newBaseTI("double",6 /* tkDouble */);
+    newBaseTI("boolean",7 /* tkBool */);
+    newBaseTI("tTypeInfoProcVar",8 /* tkProcVar */);
+    newBaseTI("tTypeInfoMethodVar",9 /* tkMethod */,rtl.tTypeInfoProcVar);
+    newBaseTI("tTypeInfoArray",10 /* tkArray */);
+    newBaseTI("tTypeInfoDynArray",11 /* tkDynArray */);
+    newBaseTI("tTypeInfoPointer",15 /* tkPointer */);
+    var t = newBaseTI("pointer",15 /* tkPointer */,rtl.tTypeInfoPointer);
+    t.reftype = null;
+    newBaseTI("jsvalue",16 /* tkJSValue */);
+    newBaseTI("tTypeInfoRefToProcVar",17 /* tkRefToProcVar */,rtl.tTypeInfoProcVar);
+
+    // member kinds
+    rtl.tTypeMember = {};
+    function newMember(name,kind){
+      var m = Object.create(rtl.tTypeMember);
+      m.name = name;
+      m.kind = kind;
+      rtl[name] = m;
+    };
+    newMember("tTypeMemberField",1); // tmkField
+    newMember("tTypeMemberMethod",2); // tmkMethod
+    newMember("tTypeMemberProperty",3); // tmkProperty
+
+    // base object for storing members: a simple object
+    rtl.tTypeMembers = {};
+
+    // tTypeInfoStruct - base object for tTypeInfoClass, tTypeInfoRecord, tTypeInfoInterface
+    var tis = newBaseTI("tTypeInfoStruct",0);
+    tis.$addMember = function(name,ancestor,options){
+      if (rtl.debug_rtti){
+        if (!rtl.hasString(name) || (name.charAt()==='$')) throw 'invalid member "'+name+'", this="'+this.name+'"';
+        if (!rtl.is(ancestor,rtl.tTypeMember)) throw 'invalid ancestor "'+ancestor+':'+ancestor.name+'", "'+this.name+'.'+name+'"';
+        if ((options!=undefined) && (typeof(options)!='object')) throw 'invalid options "'+options+'", "'+this.name+'.'+name+'"';
+      };
+      var t = Object.create(ancestor);
+      t.name = name;
+      this.members[name] = t;
+      this.names.push(name);
+      if (rtl.isObject(options)){
+        for (var key in options) if (options.hasOwnProperty(key)) t[key] = options[key];
+      };
+      return t;
+    };
+    tis.addField = function(name,type,options){
+      var t = this.$addMember(name,rtl.tTypeMemberField,options);
+      if (rtl.debug_rtti){
+        if (!rtl.is(type,rtl.tTypeInfo)) throw 'invalid type "'+type+'", "'+this.name+'.'+name+'"';
+      };
+      t.typeinfo = type;
+      this.fields.push(name);
+      return t;
+    };
+    tis.addFields = function(){
+      var i=0;
+      while(i<arguments.length){
+        var name = arguments[i++];
+        var type = arguments[i++];
+        if ((i<arguments.length) && (typeof(arguments[i])==='object')){
+          this.addField(name,type,arguments[i++]);
+        } else {
+          this.addField(name,type);
+        };
+      };
+    };
+    tis.addMethod = function(name,methodkind,params,result,options){
+      var t = this.$addMember(name,rtl.tTypeMemberMethod,options);
+      t.methodkind = methodkind;
+      t.procsig = rtl.newTIProcSig(params);
+      t.procsig.resulttype = result?result:null;
+      this.methods.push(name);
+      return t;
+    };
+    tis.addProperty = function(name,flags,result,getter,setter,options){
+      var t = this.$addMember(name,rtl.tTypeMemberProperty,options);
+      t.flags = flags;
+      t.typeinfo = result;
+      t.getter = getter;
+      t.setter = setter;
+      // Note: in options: params, stored, defaultvalue
+      if (rtl.isArray(t.params)) t.params = rtl.newTIParams(t.params);
+      this.properties.push(name);
+      if (!rtl.isString(t.stored)) t.stored = "";
+      return t;
+    };
+    tis.getField = function(index){
+      return this.members[this.fields[index]];
+    };
+    tis.getMethod = function(index){
+      return this.members[this.methods[index]];
+    };
+    tis.getProperty = function(index){
+      return this.members[this.properties[index]];
+    };
+
+    newBaseTI("tTypeInfoRecord",12 /* tkRecord */,rtl.tTypeInfoStruct);
+    newBaseTI("tTypeInfoClass",13 /* tkClass */,rtl.tTypeInfoStruct);
+    newBaseTI("tTypeInfoClassRef",14 /* tkClassRef */);
+    newBaseTI("tTypeInfoInterface",15 /* tkInterface */,rtl.tTypeInfoStruct);
+  },
+
+  tSectionRTTI: {
+    $module: null,
+    $inherited: function(name,ancestor,o){
+      if (rtl.debug_rtti){
+        rtl.debug('tSectionRTTI.newTI "'+(this.$module?this.$module.$name:"(no module)")
+          +'"."'+name+'" ('+ancestor.name+') '+(o?'init':'forward'));
+      };
+      var t = this[name];
+      if (t){
+        if (!t.$forward) throw 'duplicate type "'+name+'"';
+        if (!ancestor.isPrototypeOf(t)) throw 'typeinfo ancestor mismatch "'+name+'" ancestor="'+ancestor.name+'" t.name="'+t.name+'"';
+      } else {
+        t = Object.create(ancestor);
+        t.name = name;
+        t.$module = this.$module;
+        this[name] = t;
+      }
+      if (o){
+        delete t.$forward;
+        for (var key in o) if (o.hasOwnProperty(key)) t[key]=o[key];
+      } else {
+        t.$forward = true;
+      }
+      return t;
+    },
+    $Scope: function(name,ancestor,o){
+      var t=this.$inherited(name,ancestor,o);
+      t.members = {};
+      t.names = [];
+      t.fields = [];
+      t.methods = [];
+      t.properties = [];
+      return t;
+    },
+    $TI: function(name,kind,o){ var t=this.$inherited(name,rtl.tTypeInfo,o); t.kind = kind; return t; },
+    $Int: function(name,o){ return this.$inherited(name,rtl.tTypeInfoInteger,o); },
+    $Enum: function(name,o){ return this.$inherited(name,rtl.tTypeInfoEnum,o); },
+    $Set: function(name,o){ return this.$inherited(name,rtl.tTypeInfoSet,o); },
+    $StaticArray: function(name,o){ return this.$inherited(name,rtl.tTypeInfoArray,o); },
+    $DynArray: function(name,o){ return this.$inherited(name,rtl.tTypeInfoDynArray,o); },
+    $ProcVar: function(name,o){ return this.$inherited(name,rtl.tTypeInfoProcVar,o); },
+    $RefToProcVar: function(name,o){ return this.$inherited(name,rtl.tTypeInfoRefToProcVar,o); },
+    $MethodVar: function(name,o){ return this.$inherited(name,rtl.tTypeInfoMethodVar,o); },
+    $Record: function(name,o){ return this.$Scope(name,rtl.tTypeInfoRecord,o); },
+    $Class: function(name,o){ return this.$Scope(name,rtl.tTypeInfoClass,o); },
+    $ClassRef: function(name,o){ return this.$inherited(name,rtl.tTypeInfoClassRef,o); },
+    $Pointer: function(name,o){ return this.$inherited(name,rtl.tTypeInfoPointer,o); },
+    $Interface: function(name,o){ return this.$Scope(name,rtl.tTypeInfoInterface,o); }
+  },
+
+  newTIParam: function(param){
+    // param is an array, 0=name, 1=type, 2=optional flags
+    var t = {
+      name: param[0],
+      typeinfo: param[1],
+      flags: (rtl.isNumber(param[2]) ? param[2] : 0)
+    };
+    return t;
+  },
+
+  newTIParams: function(list){
+    // list: optional array of [paramname,typeinfo,optional flags]
+    var params = [];
+    if (rtl.isArray(list)){
+      for (var i=0; i<list.length; i++) params.push(rtl.newTIParam(list[i]));
+    };
+    return params;
+  },
+
+  newTIProcSig: function(params,result,flags){
+    var s = {
+      params: rtl.newTIParams(params),
+      resulttype: result,
+      flags: flags
+    };
+    return s;
+  }
+}
+rtl.module("System",[],function () {
+  "use strict";
+  var $mod = this;
+  var $impl = $mod.$impl;
+  this.LineEnding = "\n";
+  this.sLineBreak = $mod.LineEnding;
+  rtl.createClass($mod,"TObject",null,function () {
+    this.$init = function () {
+    };
+    this.$final = function () {
+    };
+    this.Create = function () {
+    };
+    this.AfterConstruction = function () {
+    };
+    this.BeforeDestruction = function () {
+    };
+  });
+  this.Random = function (Range) {
+    return Math.floor(Math.random()*Range);
+  };
+  this.Trunc = function (A) {
+    if (!Math.trunc) {
+      Math.trunc = function(v) {
+        v = +v;
+        if (!isFinite(v)) return v;
+        return (v - v % 1) || (v < 0 ? -0 : v === 0 ? v : 0);
+      };
+    }
+    $mod.Trunc = Math.trunc;
+    return Math.trunc(A);
+  };
+  this.Writeln = function () {
+    var i = 0;
+    var l = 0;
+    var s = "";
+    l = rtl.length(arguments) - 1;
+    if ($impl.WriteCallBack != null) {
+      for (var $l1 = 0, $end2 = l; $l1 <= $end2; $l1++) {
+        i = $l1;
+        $impl.WriteCallBack(arguments[i],i === l);
+      };
+    } else {
+      s = $impl.WriteBuf;
+      for (var $l3 = 0, $end4 = l; $l3 <= $end4; $l3++) {
+        i = $l3;
+        s = s + ("" + arguments[i]);
+      };
+      console.log(s);
+      $impl.WriteBuf = "";
+    };
+  };
+  this.SetWriteCallBack = function (H) {
+    var Result = null;
+    Result = $impl.WriteCallBack;
+    $impl.WriteCallBack = H;
+    return Result;
+  };
+  $mod.$init = function () {
+    rtl.exitcode = 0;
+  };
+},null,function () {
+  "use strict";
+  var $mod = this;
+  var $impl = $mod.$impl;
+  $impl.WriteBuf = "";
+  $impl.WriteCallBack = null;
+});
+rtl.module("JS",["System"],function () {
+  "use strict";
+  var $mod = this;
+});
+rtl.module("SysUtils",["System","JS"],function () {
+  "use strict";
+  var $mod = this;
+  rtl.createClass($mod,"Exception",pas.System.TObject,function () {
+    this.$init = function () {
+      pas.System.TObject.$init.call(this);
+      this.fMessage = "";
+    };
+    this.Create$1 = function (Msg) {
+      this.fMessage = Msg;
+    };
+  });
+  this.IntToStr = function (Value) {
+    var Result = "";
+    Result = "" + Value;
+    return Result;
+  };
+  rtl.createClass($mod,"TFormatSettings",pas.System.TObject,function () {
+  });
+  this.FormatSettings = null;
+  $mod.$init = function () {
+    $mod.FormatSettings = $mod.TFormatSettings.$create("Create");
+  };
+});
+rtl.module("math",["System","SysUtils"],function () {
+  "use strict";
+  var $mod = this;
+  this.Floor = function (A) {
+    var Result = 0;
+    Result = pas.System.Trunc(Math.floor(A));
+    return Result;
+  };
+});
+rtl.module("Noise",["System","SysUtils","math"],function () {
+  "use strict";
+  var $mod = this;
+  this.kNoisekPerumationMax = 256;
+  rtl.createClass($mod,"TNoise",pas.System.TObject,function () {
+    this.$init = function () {
+      pas.System.TObject.$init.call(this);
+      this.repeatValue = 0;
+      this.p = rtl.arraySetLength(null,0,512);
+    };
+    this.$final = function () {
+      this.p = undefined;
+      pas.System.TObject.$final.call(this);
+    };
+    this.Create$2 = function (seed) {
+      var i = 0;
+      this.repeatValue = -1;
+      for (i = 0; i <= 511; i++) this.p[i] = seed[i % 256];
+    };
+    this.GetValue = function (x, y, z) {
+      var Self = this;
+      var Result = 0.0;
+      function FMod(a, b) {
+        var Result = 0.0;
+        Result = a - (b * pas.System.Trunc(a / b));
+        return Result;
+      };
+      var xi = 0;
+      var yi = 0;
+      var zi = 0;
+      var xf = 0.0;
+      var yf = 0.0;
+      var zf = 0.0;
+      var u = 0.0;
+      var v = 0.0;
+      var w = 0.0;
+      var aaa = 0;
+      var aba = 0;
+      var aab = 0;
+      var abb = 0;
+      var baa = 0;
+      var bba = 0;
+      var bab = 0;
+      var bbb = 0;
+      var x1 = 0.0;
+      var x2 = 0.0;
+      var y1 = 0.0;
+      var y2 = 0.0;
+      if (Self.repeatValue > 0) {
+        x = FMod(x,Self.repeatValue);
+        y = FMod(y,Self.repeatValue);
+        z = FMod(z,Self.repeatValue);
+      };
+      xi = pas.math.Floor(x) & 255;
+      yi = pas.math.Floor(y) & 255;
+      zi = pas.math.Floor(z) & 255;
+      xf = x - pas.math.Floor(x);
+      yf = y - pas.math.Floor(y);
+      zf = z - pas.math.Floor(z);
+      u = Self.Fade(xf);
+      v = Self.Fade(yf);
+      w = Self.Fade(zf);
+      aaa = Self.p[Self.p[Self.p[xi] + yi] + zi];
+      aba = Self.p[Self.p[Self.p[xi] + Self.Inc(yi)] + zi];
+      aab = Self.p[Self.p[Self.p[xi] + yi] + Self.Inc(zi)];
+      abb = Self.p[Self.p[Self.p[xi] + Self.Inc(yi)] + Self.Inc(zi)];
+      baa = Self.p[Self.p[Self.p[Self.Inc(xi)] + yi] + zi];
+      bba = Self.p[Self.p[Self.p[Self.Inc(xi)] + Self.Inc(yi)] + zi];
+      bab = Self.p[Self.p[Self.p[Self.Inc(xi)] + yi] + Self.Inc(zi)];
+      bbb = Self.p[Self.p[Self.p[Self.Inc(xi)] + Self.Inc(yi)] + Self.Inc(zi)];
+      x1 = Self.Lerp(Self.Grad(aaa,xf,yf,zf),Self.Grad(baa,xf - 1,yf,zf),u);
+      x2 = Self.Lerp(Self.Grad(aba,xf,yf - 1,zf),Self.Grad(bba,xf - 1,yf - 1,zf),u);
+      y1 = Self.Lerp(x1,x2,v);
+      x1 = Self.Lerp(Self.Grad(aab,xf,yf,zf - 1),Self.Grad(bab,xf - 1,yf,zf - 1),u);
+      x2 = Self.Lerp(Self.Grad(abb,xf,yf - 1,zf - 1),Self.Grad(bbb,xf - 1,yf - 1,zf - 1),u);
+      y2 = Self.Lerp(x1,x2,v);
+      Result = (Self.Lerp(y1,y2,w) + 1) / 2;
+      return Result;
+    };
+    this.GetValue$1 = function (x, y, z, octaves, persistence) {
+      var Result = 0.0;
+      var total = 0;
+      var frequency = 1;
+      var amplitude = 1;
+      var maxValue = 0;
+      var i = 0;
+      for (var $l1 = 0, $end2 = octaves - 1; $l1 <= $end2; $l1++) {
+        i = $l1;
+        total += this.GetValue(x * frequency,y * frequency,z * frequency) * amplitude;
+        maxValue += amplitude;
+        amplitude *= persistence;
+        frequency *= 2;
+      };
+      Result = total / maxValue;
+      return Result;
+    };
+    this.GetNoise = function (x, y, width, height, scale, frequency) {
+      var Result = 0.0;
+      var nx = 0.0;
+      var ny = 0.0;
+      nx = (x / width) - 0.5;
+      ny = (y / height) - 0.5;
+      Result = (this.GetValue$1(nx * scale,ny * scale,0,frequency,0.5) / 2) + 0.5;
+      return Result;
+    };
+    this.Inc = function (num) {
+      var Result = 0;
+      num += 1;
+      if (this.repeatValue > 0) num = num % this.repeatValue;
+      Result = num;
+      return Result;
+    };
+    this.Grad = function (hash, x, y, z) {
+      var Result = 0.0;
+      var $tmp1 = hash & 0xF;
+      if ($tmp1 === 0x0) {
+        Result = x + y}
+       else if ($tmp1 === 0x1) {
+        Result = -x + y}
+       else if ($tmp1 === 0x2) {
+        Result = x - y}
+       else if ($tmp1 === 0x3) {
+        Result = -x - y}
+       else if ($tmp1 === 0x4) {
+        Result = x + z}
+       else if ($tmp1 === 0x5) {
+        Result = -x + z}
+       else if ($tmp1 === 0x6) {
+        Result = x - z}
+       else if ($tmp1 === 0x7) {
+        Result = -x - z}
+       else if ($tmp1 === 0x8) {
+        Result = y + z}
+       else if ($tmp1 === 0x9) {
+        Result = -y + z}
+       else if ($tmp1 === 0xA) {
+        Result = y - z}
+       else if ($tmp1 === 0xB) {
+        Result = -y - z}
+       else if ($tmp1 === 0xC) {
+        Result = y + x}
+       else if ($tmp1 === 0xD) {
+        Result = -y + z}
+       else if ($tmp1 === 0xE) {
+        Result = y - x}
+       else if ($tmp1 === 0xF) {
+        Result = -y - z}
+       else {
+        Result = 0;
+      };
+      return Result;
+    };
+    this.Fade = function (t) {
+      var Result = 0.0;
+      Result = ((t * t) * t) * ((t * ((t * 6) - 15)) + 10);
+      return Result;
+    };
+    this.Lerp = function (a, b, x) {
+      var Result = 0.0;
+      Result = a + (x * (b - a));
+      return Result;
+    };
+  });
+  this.RandomNoiseSeed = function (seed) {
+    var Result = rtl.arraySetLength(null,0,256);
+    var i = 0;
+    for (i = 0; i <= 255; i++) Result[i] = pas.System.Random(256);
+    return Result;
+  };
+});
+rtl.module("Matrix",["System","JS"],function () {
+  "use strict";
+  var $mod = this;
+  rtl.createClass($mod,"TMatrix",pas.System.TObject,function () {
+    this.$init = function () {
+      pas.System.TObject.$init.call(this);
+      this.table = null;
+      this.width = 0;
+      this.height = 0;
+    };
+    this.$final = function () {
+      this.table = undefined;
+      pas.System.TObject.$final.call(this);
+    };
+    this.Create$1 = function (w, h) {
+      this.width = w;
+      this.height = h;
+      this.table = new Array(this.width * this.height);
+    };
+    this.SetValue = function (x, y, value) {
+      this.table[this.IndexFor(x,y)] = value;
+    };
+    this.GetValue = function (x, y) {
+      var Result = undefined;
+      Result = this.table[this.IndexFor(x,y)];
+      return Result;
+    };
+    this.GetWidth = function () {
+      var Result = 0;
+      Result = this.width;
+      return Result;
+    };
+    this.GetHeight = function () {
+      var Result = 0;
+      Result = this.height;
+      return Result;
+    };
+    this.IndexFor = function (x, y) {
+      var Result = 0;
+      Result = x + (y * this.height);
+      return Result;
+    };
+  });
+});
+rtl.module("Web",["System","JS"],function () {
+  "use strict";
+  var $mod = this;
+});
+rtl.module("webgl",["System","JS","Web"],function () {
+  "use strict";
+  var $mod = this;
+});
+rtl.module("GLTypes",["System","webgl","JS","math","SysUtils"],function () {
+  "use strict";
+  var $mod = this;
+  this.TVec2 = function (s) {
+    if (s) {
+      this.x = s.x;
+      this.y = s.y;
+    } else {
+      this.x = 0.0;
+      this.y = 0.0;
+    };
+    this.$equal = function (b) {
+      return (this.x === b.x) && (this.y === b.y);
+    };
+  };
+  this.TVec3 = function (s) {
+    if (s) {
+      this.x = s.x;
+      this.y = s.y;
+      this.z = s.z;
+    } else {
+      this.x = 0.0;
+      this.y = 0.0;
+      this.z = 0.0;
+    };
+    this.$equal = function (b) {
+      return (this.x === b.x) && ((this.y === b.y) && (this.z === b.z));
+    };
+  };
+  this.V3 = function (x, y, z) {
+    var Result = new $mod.TVec3();
+    Result.x = x;
+    Result.y = y;
+    Result.z = z;
+    return Result;
+  };
+  this.Divide = function (v, amount) {
+    var Result = new $mod.TVec3();
+    Result = new $mod.TVec3($mod.V3(v.x / amount,v.y / amount,v.z / amount));
+    return Result;
+  };
+  this.Magnitude = function (v) {
+    var Result = 0.0;
+    Result = Math.sqrt((Math.pow(v.x,2) + Math.pow(v.y,2)) + Math.pow(v.z,2));
+    return Result;
+  };
+  this.Normalize = function (v) {
+    var Result = new $mod.TVec3();
+    Result = new $mod.TVec3($mod.Divide(new $mod.TVec3(v),$mod.Magnitude(new $mod.TVec3(v))));
+    return Result;
+  };
+  this.V2 = function (x, y) {
+    var Result = new $mod.TVec2();
+    Result.x = x;
+    Result.y = y;
+    return Result;
+  };
+});
+rtl.module("browserconsole",["System","JS","Web"],function () {
+  "use strict";
+  var $mod = this;
+  var $impl = $mod.$impl;
+  this.DefaultMaxConsoleLines = 25;
+  this.DefaultConsoleStyle = (((((((((((".pasconsole { " + pas.System.sLineBreak) + "font-family: courier;") + pas.System.sLineBreak) + "font-size: 14px;") + pas.System.sLineBreak) + "background: #FFFFFF;") + pas.System.sLineBreak) + "color: #000000;") + pas.System.sLineBreak) + "display: block;") + pas.System.sLineBreak) + "}";
+  this.ConsoleElementID = "";
+  this.ConsoleStyle = "";
+  this.MaxConsoleLines = 0;
+  this.ConsoleLinesToBrowserLog = false;
+  this.ResetConsole = function () {
+    if ($impl.LinesParent === null) return;
+    while ($impl.LinesParent.firstElementChild !== null) $impl.LinesParent.removeChild($impl.LinesParent.firstElementChild);
+    $impl.AppendLine();
+  };
+  this.InitConsole = function () {
+    if ($impl.ConsoleElement === null) return;
+    if ($impl.ConsoleElement.nodeName.toLowerCase() !== "body") {
+      while ($impl.ConsoleElement.firstElementChild !== null) $impl.ConsoleElement.removeChild($impl.ConsoleElement.firstElementChild);
+    };
+    $impl.StyleElement = document.createElement("style");
+    $impl.StyleElement.innerText = $mod.ConsoleStyle;
+    $impl.ConsoleElement.appendChild($impl.StyleElement);
+    $impl.LinesParent = document.createElement("div");
+    $impl.ConsoleElement.appendChild($impl.LinesParent);
+  };
+  this.HookConsole = function () {
+    $impl.ConsoleElement = null;
+    if ($mod.ConsoleElementID !== "") $impl.ConsoleElement = document.getElementById($mod.ConsoleElementID);
+    if ($impl.ConsoleElement === null) $impl.ConsoleElement = document.body;
+    if ($impl.ConsoleElement === null) return;
+    $mod.InitConsole();
+    $mod.ResetConsole();
+    pas.System.SetWriteCallBack($impl.WriteConsole);
+  };
+  $mod.$init = function () {
+    $mod.ConsoleLinesToBrowserLog = true;
+    $mod.ConsoleElementID = "pasjsconsole";
+    $mod.ConsoleStyle = $mod.DefaultConsoleStyle;
+    $mod.MaxConsoleLines = 25;
+    $mod.HookConsole();
+  };
+},null,function () {
+  "use strict";
+  var $mod = this;
+  var $impl = $mod.$impl;
+  $impl.LastLine = null;
+  $impl.StyleElement = null;
+  $impl.LinesParent = null;
+  $impl.ConsoleElement = null;
+  $impl.AppendLine = function () {
+    var CurrentCount = 0;
+    var S = null;
+    CurrentCount = 0;
+    S = $impl.LinesParent.firstChild;
+    while (S != null) {
+      CurrentCount += 1;
+      S = S.nextSibling;
+    };
+    while (CurrentCount > $mod.MaxConsoleLines) {
+      CurrentCount -= 1;
+      $impl.LinesParent.removeChild($impl.LinesParent.firstChild);
+    };
+    $impl.LastLine = document.createElement("div");
+    $impl.LastLine.className = "pasconsole";
+    $impl.LinesParent.appendChild($impl.LastLine);
+  };
+  $impl.WriteConsole = function (S, NewLine) {
+    var CL = "";
+    CL = $impl.LastLine.innerText;
+    CL = CL + ("" + S);
+    $impl.LastLine.innerText = CL;
+    if (NewLine) {
+      if ($mod.ConsoleLinesToBrowserLog) window.console.log(CL);
+      $impl.AppendLine();
+    };
+  };
+});
+rtl.module("Mat4",["System","browserconsole","JS","math"],function () {
+  "use strict";
+  var $mod = this;
+  var $impl = $mod.$impl;
+  rtl.createClass($mod,"TMat4",pas.System.TObject,function () {
+    this.$init = function () {
+      pas.System.TObject.$init.call(this);
+      this.RawComponents = rtl.arraySetLength(null,0.0,4,4);
+    };
+    this.$final = function () {
+      this.RawComponents = undefined;
+      pas.System.TObject.$final.call(this);
+    };
+    this.Identity = function () {
+      this.RawComponents[0][0] = 1.0;
+      this.RawComponents[0][1] = 0.0;
+      this.RawComponents[0][2] = 0.0;
+      this.RawComponents[0][3] = 0.0;
+      this.RawComponents[1][0] = 0.0;
+      this.RawComponents[1][1] = 1.0;
+      this.RawComponents[1][2] = 0.0;
+      this.RawComponents[1][3] = 0.0;
+      this.RawComponents[2][0] = 0.0;
+      this.RawComponents[2][1] = 0.0;
+      this.RawComponents[2][2] = 1.0;
+      this.RawComponents[2][3] = 0.0;
+      this.RawComponents[3][0] = 0.0;
+      this.RawComponents[3][1] = 0.0;
+      this.RawComponents[3][2] = 0.0;
+      this.RawComponents[3][3] = 1.0;
+    };
+    this.Translate = function (tx, ty, tz) {
+      this.RawComponents[0][0] = 1.0;
+      this.RawComponents[0][1] = 0.0;
+      this.RawComponents[0][2] = 0.0;
+      this.RawComponents[0][3] = 0.0;
+      this.RawComponents[1][0] = 0.0;
+      this.RawComponents[1][1] = 1.0;
+      this.RawComponents[1][2] = 0.0;
+      this.RawComponents[1][3] = 0.0;
+      this.RawComponents[2][0] = 0.0;
+      this.RawComponents[2][1] = 0.0;
+      this.RawComponents[2][2] = 1.0;
+      this.RawComponents[2][3] = 0.0;
+      this.RawComponents[3][0] = tx;
+      this.RawComponents[3][1] = ty;
+      this.RawComponents[3][2] = tz;
+      this.RawComponents[3][3] = 1.0;
+    };
+    this.Perspective = function (fovy, Aspect, zNear, zFar) {
+      var Sine = 0.0;
+      var Cotangent = 0.0;
+      var ZDelta = 0.0;
+      var Radians = 0.0;
+      Radians = (fovy * 0.5) * 0.017453292519944444;
+      ZDelta = zFar - zNear;
+      Sine = Math.sin(Radians);
+      if (!(((ZDelta === 0) || (Sine === 0)) || (Aspect === 0))) {
+        Cotangent = Math.cos(Radians) / Sine;
+        this.RawComponents = $impl.Matrix4x4Identity.RawComponents.slice(0);
+        this.RawComponents[0][0] = Cotangent / Aspect;
+        this.RawComponents[1][1] = Cotangent;
+        this.RawComponents[2][2] = -(zFar + zNear) / ZDelta;
+        this.RawComponents[2][3] = -1 - 0;
+        this.RawComponents[3][2] = -((2.0 * zNear) * zFar) / ZDelta;
+        this.RawComponents[3][3] = 0.0;
+      };
+    };
+    this.Multiply = function (m) {
+      var Result = null;
+      Result = $mod.TMat4.$create("Identity");
+      Result.RawComponents[0][0] = (((m.RawComponents[0][0] * this.RawComponents[0][0]) + (m.RawComponents[0][1] * this.RawComponents[1][0])) + (m.RawComponents[0][2] * this.RawComponents[2][0])) + (m.RawComponents[0][3] * this.RawComponents[3][0]);
+      Result.RawComponents[0][1] = (((m.RawComponents[0][0] * this.RawComponents[0][1]) + (m.RawComponents[0][1] * this.RawComponents[1][1])) + (m.RawComponents[0][2] * this.RawComponents[2][1])) + (m.RawComponents[0][3] * this.RawComponents[3][1]);
+      Result.RawComponents[0][2] = (((m.RawComponents[0][0] * this.RawComponents[0][2]) + (m.RawComponents[0][1] * this.RawComponents[1][2])) + (m.RawComponents[0][2] * this.RawComponents[2][2])) + (m.RawComponents[0][3] * this.RawComponents[3][2]);
+      Result.RawComponents[0][3] = (((m.RawComponents[0][0] * this.RawComponents[0][3]) + (m.RawComponents[0][1] * this.RawComponents[1][3])) + (m.RawComponents[0][2] * this.RawComponents[2][3])) + (m.RawComponents[0][3] * this.RawComponents[3][3]);
+      Result.RawComponents[1][0] = (((m.RawComponents[1][0] * this.RawComponents[0][0]) + (m.RawComponents[1][1] * this.RawComponents[1][0])) + (m.RawComponents[1][2] * this.RawComponents[2][0])) + (m.RawComponents[1][3] * this.RawComponents[3][0]);
+      Result.RawComponents[1][1] = (((m.RawComponents[1][0] * this.RawComponents[0][1]) + (m.RawComponents[1][1] * this.RawComponents[1][1])) + (m.RawComponents[1][2] * this.RawComponents[2][1])) + (m.RawComponents[1][3] * this.RawComponents[3][1]);
+      Result.RawComponents[1][2] = (((m.RawComponents[1][0] * this.RawComponents[0][2]) + (m.RawComponents[1][1] * this.RawComponents[1][2])) + (m.RawComponents[1][2] * this.RawComponents[2][2])) + (m.RawComponents[1][3] * this.RawComponents[3][2]);
+      Result.RawComponents[1][3] = (((m.RawComponents[1][0] * this.RawComponents[0][3]) + (m.RawComponents[1][1] * this.RawComponents[1][3])) + (m.RawComponents[1][2] * this.RawComponents[2][3])) + (m.RawComponents[1][3] * this.RawComponents[3][3]);
+      Result.RawComponents[2][0] = (((m.RawComponents[2][0] * this.RawComponents[0][0]) + (m.RawComponents[2][1] * this.RawComponents[1][0])) + (m.RawComponents[2][2] * this.RawComponents[2][0])) + (m.RawComponents[2][3] * this.RawComponents[3][0]);
+      Result.RawComponents[2][1] = (((m.RawComponents[2][0] * this.RawComponents[0][1]) + (m.RawComponents[2][1] * this.RawComponents[1][1])) + (m.RawComponents[2][2] * this.RawComponents[2][1])) + (m.RawComponents[2][3] * this.RawComponents[3][1]);
+      Result.RawComponents[2][2] = (((m.RawComponents[2][0] * this.RawComponents[0][2]) + (m.RawComponents[2][1] * this.RawComponents[1][2])) + (m.RawComponents[2][2] * this.RawComponents[2][2])) + (m.RawComponents[2][3] * this.RawComponents[3][2]);
+      Result.RawComponents[2][3] = (((m.RawComponents[2][0] * this.RawComponents[0][3]) + (m.RawComponents[2][1] * this.RawComponents[1][3])) + (m.RawComponents[2][2] * this.RawComponents[2][3])) + (m.RawComponents[2][3] * this.RawComponents[3][3]);
+      Result.RawComponents[3][0] = (((m.RawComponents[3][0] * this.RawComponents[0][0]) + (m.RawComponents[3][1] * this.RawComponents[1][0])) + (m.RawComponents[3][2] * this.RawComponents[2][0])) + (m.RawComponents[3][3] * this.RawComponents[3][0]);
+      Result.RawComponents[3][1] = (((m.RawComponents[3][0] * this.RawComponents[0][1]) + (m.RawComponents[3][1] * this.RawComponents[1][1])) + (m.RawComponents[3][2] * this.RawComponents[2][1])) + (m.RawComponents[3][3] * this.RawComponents[3][1]);
+      Result.RawComponents[3][2] = (((m.RawComponents[3][0] * this.RawComponents[0][2]) + (m.RawComponents[3][1] * this.RawComponents[1][2])) + (m.RawComponents[3][2] * this.RawComponents[2][2])) + (m.RawComponents[3][3] * this.RawComponents[3][2]);
+      Result.RawComponents[3][3] = (((m.RawComponents[3][0] * this.RawComponents[0][3]) + (m.RawComponents[3][1] * this.RawComponents[1][3])) + (m.RawComponents[3][2] * this.RawComponents[2][3])) + (m.RawComponents[3][3] * this.RawComponents[3][3]);
+      return Result;
+    };
+    this.Inverse = function () {
+      var Result = null;
+      var t0 = 0.0;
+      var t4 = 0.0;
+      var t8 = 0.0;
+      var t12 = 0.0;
+      var d = 0.0;
+      t0 = ((((((this.RawComponents[1][1] * this.RawComponents[2][2]) * this.RawComponents[3][3]) - ((this.RawComponents[1][1] * this.RawComponents[2][3]) * this.RawComponents[3][2])) - ((this.RawComponents[2][1] * this.RawComponents[1][2]) * this.RawComponents[3][3])) + ((this.RawComponents[2][1] * this.RawComponents[1][3]) * this.RawComponents[3][2])) + ((this.RawComponents[3][1] * this.RawComponents[1][2]) * this.RawComponents[2][3])) - ((this.RawComponents[3][1] * this.RawComponents[1][3]) * this.RawComponents[2][2]);
+      t4 = ((((-((this.RawComponents[1][0] * this.RawComponents[2][2]) * this.RawComponents[3][3]) + ((this.RawComponents[1][0] * this.RawComponents[2][3]) * this.RawComponents[3][2])) + ((this.RawComponents[2][0] * this.RawComponents[1][2]) * this.RawComponents[3][3])) - ((this.RawComponents[2][0] * this.RawComponents[1][3]) * this.RawComponents[3][2])) - ((this.RawComponents[3][0] * this.RawComponents[1][2]) * this.RawComponents[2][3])) + ((this.RawComponents[3][0] * this.RawComponents[1][3]) * this.RawComponents[2][2]);
+      t8 = ((((((this.RawComponents[1][0] * this.RawComponents[2][1]) * this.RawComponents[3][3]) - ((this.RawComponents[1][0] * this.RawComponents[2][3]) * this.RawComponents[3][1])) - ((this.RawComponents[2][0] * this.RawComponents[1][1]) * this.RawComponents[3][3])) + ((this.RawComponents[2][0] * this.RawComponents[1][3]) * this.RawComponents[3][1])) + ((this.RawComponents[3][0] * this.RawComponents[1][1]) * this.RawComponents[2][3])) - ((this.RawComponents[3][0] * this.RawComponents[1][3]) * this.RawComponents[2][1]);
+      t12 = ((((-((this.RawComponents[1][0] * this.RawComponents[2][1]) * this.RawComponents[3][2]) + ((this.RawComponents[1][0] * this.RawComponents[2][2]) * this.RawComponents[3][1])) + ((this.RawComponents[2][0] * this.RawComponents[1][1]) * this.RawComponents[3][2])) - ((this.RawComponents[2][0] * this.RawComponents[1][2]) * this.RawComponents[3][1])) - ((this.RawComponents[3][0] * this.RawComponents[1][1]) * this.RawComponents[2][2])) + ((this.RawComponents[3][0] * this.RawComponents[1][2]) * this.RawComponents[2][1]);
+      d = (((this.RawComponents[0][0] * t0) + (this.RawComponents[0][1] * t4)) + (this.RawComponents[0][2] * t8)) + (this.RawComponents[0][3] * t12);
+      Result = $mod.TMat4.$create("Identity");
+      if (d !== 0.0) {
+        d = 1.0 / d;
+        Result.RawComponents[0][0] = t0 * d;
+        Result.RawComponents[0][1] = (((((-((this.RawComponents[0][1] * this.RawComponents[2][2]) * this.RawComponents[3][3]) + ((this.RawComponents[0][1] * this.RawComponents[2][3]) * this.RawComponents[3][2])) + ((this.RawComponents[2][1] * this.RawComponents[0][2]) * this.RawComponents[3][3])) - ((this.RawComponents[2][1] * this.RawComponents[0][3]) * this.RawComponents[3][2])) - ((this.RawComponents[3][1] * this.RawComponents[0][2]) * this.RawComponents[2][3])) + ((this.RawComponents[3][1] * this.RawComponents[0][3]) * this.RawComponents[2][2])) * d;
+        Result.RawComponents[0][2] = (((((((this.RawComponents[0][1] * this.RawComponents[1][2]) * this.RawComponents[3][3]) - ((this.RawComponents[0][1] * this.RawComponents[1][3]) * this.RawComponents[3][2])) - ((this.RawComponents[1][1] * this.RawComponents[0][2]) * this.RawComponents[3][3])) + ((this.RawComponents[1][1] * this.RawComponents[0][3]) * this.RawComponents[3][2])) + ((this.RawComponents[3][1] * this.RawComponents[0][2]) * this.RawComponents[1][3])) - ((this.RawComponents[3][1] * this.RawComponents[0][3]) * this.RawComponents[1][2])) * d;
+        Result.RawComponents[0][3] = (((((-((this.RawComponents[0][1] * this.RawComponents[1][2]) * this.RawComponents[2][3]) + ((this.RawComponents[0][1] * this.RawComponents[1][3]) * this.RawComponents[2][2])) + ((this.RawComponents[1][1] * this.RawComponents[0][2]) * this.RawComponents[2][3])) - ((this.RawComponents[1][1] * this.RawComponents[0][3]) * this.RawComponents[2][2])) - ((this.RawComponents[2][1] * this.RawComponents[0][2]) * this.RawComponents[1][3])) + ((this.RawComponents[2][1] * this.RawComponents[0][3]) * this.RawComponents[1][2])) * d;
+        Result.RawComponents[1][0] = t4 * d;
+        Result.RawComponents[1][1] = (((((((this.RawComponents[0][0] * this.RawComponents[2][2]) * this.RawComponents[3][3]) - ((this.RawComponents[0][0] * this.RawComponents[2][3]) * this.RawComponents[3][2])) - ((this.RawComponents[2][0] * this.RawComponents[0][2]) * this.RawComponents[3][3])) + ((this.RawComponents[2][0] * this.RawComponents[0][3]) * this.RawComponents[3][2])) + ((this.RawComponents[3][0] * this.RawComponents[0][2]) * this.RawComponents[2][3])) - ((this.RawComponents[3][0] * this.RawComponents[0][3]) * this.RawComponents[2][2])) * d;
+        Result.RawComponents[1][2] = (((((-((this.RawComponents[0][0] * this.RawComponents[1][2]) * this.RawComponents[3][3]) + ((this.RawComponents[0][0] * this.RawComponents[1][3]) * this.RawComponents[3][2])) + ((this.RawComponents[1][0] * this.RawComponents[0][2]) * this.RawComponents[3][3])) - ((this.RawComponents[1][0] * this.RawComponents[0][3]) * this.RawComponents[3][2])) - ((this.RawComponents[3][0] * this.RawComponents[0][2]) * this.RawComponents[1][3])) + ((this.RawComponents[3][0] * this.RawComponents[0][3]) * this.RawComponents[1][2])) * d;
+        Result.RawComponents[1][3] = (((((((this.RawComponents[0][0] * this.RawComponents[1][2]) * this.RawComponents[2][3]) - ((this.RawComponents[0][0] * this.RawComponents[1][3]) * this.RawComponents[2][2])) - ((this.RawComponents[1][0] * this.RawComponents[0][2]) * this.RawComponents[2][3])) + ((this.RawComponents[1][0] * this.RawComponents[0][3]) * this.RawComponents[2][2])) + ((this.RawComponents[2][0] * this.RawComponents[0][2]) * this.RawComponents[1][3])) - ((this.RawComponents[2][0] * this.RawComponents[0][3]) * this.RawComponents[1][2])) * d;
+        Result.RawComponents[2][0] = t8 * d;
+        Result.RawComponents[2][1] = (((((-((this.RawComponents[0][0] * this.RawComponents[2][1]) * this.RawComponents[3][3]) + ((this.RawComponents[0][0] * this.RawComponents[2][3]) * this.RawComponents[3][1])) + ((this.RawComponents[2][0] * this.RawComponents[0][1]) * this.RawComponents[3][3])) - ((this.RawComponents[2][0] * this.RawComponents[0][3]) * this.RawComponents[3][1])) - ((this.RawComponents[3][0] * this.RawComponents[0][1]) * this.RawComponents[2][3])) + ((this.RawComponents[3][0] * this.RawComponents[0][3]) * this.RawComponents[2][1])) * d;
+        Result.RawComponents[2][2] = (((((((this.RawComponents[0][0] * this.RawComponents[1][1]) * this.RawComponents[3][3]) - ((this.RawComponents[0][0] * this.RawComponents[1][3]) * this.RawComponents[3][1])) - ((this.RawComponents[1][0] * this.RawComponents[0][1]) * this.RawComponents[3][3])) + ((this.RawComponents[1][0] * this.RawComponents[0][3]) * this.RawComponents[3][1])) + ((this.RawComponents[3][0] * this.RawComponents[0][1]) * this.RawComponents[1][3])) - ((this.RawComponents[3][0] * this.RawComponents[0][3]) * this.RawComponents[1][1])) * d;
+        Result.RawComponents[2][3] = (((((-((this.RawComponents[0][0] * this.RawComponents[1][1]) * this.RawComponents[2][3]) + ((this.RawComponents[0][0] * this.RawComponents[1][3]) * this.RawComponents[2][1])) + ((this.RawComponents[1][0] * this.RawComponents[0][1]) * this.RawComponents[2][3])) - ((this.RawComponents[1][0] * this.RawComponents[0][3]) * this.RawComponents[2][1])) - ((this.RawComponents[2][0] * this.RawComponents[0][1]) * this.RawComponents[1][3])) + ((this.RawComponents[2][0] * this.RawComponents[0][3]) * this.RawComponents[1][1])) * d;
+        Result.RawComponents[3][0] = t12 * d;
+        Result.RawComponents[3][1] = (((((((this.RawComponents[0][0] * this.RawComponents[2][1]) * this.RawComponents[3][2]) - ((this.RawComponents[0][0] * this.RawComponents[2][2]) * this.RawComponents[3][1])) - ((this.RawComponents[2][0] * this.RawComponents[0][1]) * this.RawComponents[3][2])) + ((this.RawComponents[2][0] * this.RawComponents[0][2]) * this.RawComponents[3][1])) + ((this.RawComponents[3][0] * this.RawComponents[0][1]) * this.RawComponents[2][2])) - ((this.RawComponents[3][0] * this.RawComponents[0][2]) * this.RawComponents[2][1])) * d;
+        Result.RawComponents[3][2] = (((((-((this.RawComponents[0][0] * this.RawComponents[1][1]) * this.RawComponents[3][2]) + ((this.RawComponents[0][0] * this.RawComponents[1][2]) * this.RawComponents[3][1])) + ((this.RawComponents[1][0] * this.RawComponents[0][1]) * this.RawComponents[3][2])) - ((this.RawComponents[1][0] * this.RawComponents[0][2]) * this.RawComponents[3][1])) - ((this.RawComponents[3][0] * this.RawComponents[0][1]) * this.RawComponents[1][2])) + ((this.RawComponents[3][0] * this.RawComponents[0][2]) * this.RawComponents[1][1])) * d;
+        Result.RawComponents[3][3] = (((((((this.RawComponents[0][0] * this.RawComponents[1][1]) * this.RawComponents[2][2]) - ((this.RawComponents[0][0] * this.RawComponents[1][2]) * this.RawComponents[2][1])) - ((this.RawComponents[1][0] * this.RawComponents[0][1]) * this.RawComponents[2][2])) + ((this.RawComponents[1][0] * this.RawComponents[0][2]) * this.RawComponents[2][1])) + ((this.RawComponents[2][0] * this.RawComponents[0][1]) * this.RawComponents[1][2])) - ((this.RawComponents[2][0] * this.RawComponents[0][2]) * this.RawComponents[1][1])) * d;
+      };
+      return Result;
+    };
+    this.CopyList = function () {
+      var Result = [];
+      var x = 0;
+      var y = 0;
+      var list = null;
+      list = new Array();
+      for (x = 0; x <= 3; x++) for (y = 0; y <= 3; y++) list.push(this.RawComponents[x][y]);
+      Result = list;
+      return Result;
+    };
+  });
+  $mod.$init = function () {
+    $impl.Matrix4x4Identity = $mod.TMat4.$create("Identity");
+  };
+},null,function () {
+  "use strict";
+  var $mod = this;
+  var $impl = $mod.$impl;
+  $impl.PI = 3.14159265359;
+  $impl.DEG2RAD = 3.14159265359 / 180.0;
+  $impl.Matrix4x4Identity = null;
+});
+rtl.module("GLUtils",["System","Mat4","GLTypes","browserconsole","Web","webgl","JS","math","SysUtils"],function () {
+  "use strict";
+  var $mod = this;
+  var $impl = $mod.$impl;
+  rtl.createClass($mod,"TShader",pas.System.TObject,function () {
+    this.$init = function () {
+      pas.System.TObject.$init.call(this);
+      this.gl = null;
+      this.vertexShader = null;
+      this.fragmentShader = null;
+      this.programID = null;
+    };
+    this.$final = function () {
+      this.gl = undefined;
+      this.vertexShader = undefined;
+      this.fragmentShader = undefined;
+      this.programID = undefined;
+      pas.System.TObject.$final.call(this);
+    };
+    this.Create$1 = function (context, vertexShaderSource, fragmentShaderSource) {
+      this.gl = context;
+      this.vertexShader = this.CreateShader(this.gl.VERTEX_SHADER,vertexShaderSource);
+      this.fragmentShader = this.CreateShader(this.gl.FRAGMENT_SHADER,fragmentShaderSource);
+    };
+    this.Compile = function () {
+      this.programID = this.gl.createProgram();
+      this.gl.attachShader(this.programID,this.vertexShader);
+      this.gl.attachShader(this.programID,this.fragmentShader);
+    };
+    this.Link = function () {
+      this.gl.linkProgram(this.programID);
+      if (!this.gl.getProgramParameter(this.programID,this.gl.LINK_STATUS)) {
+        $impl.Fatal(this.gl.getProgramInfoLog(this.programID));
+      };
+    };
+    this.Use = function () {
+      this.gl.useProgram(this.programID);
+    };
+    this.BindAttribLocation = function (index, name) {
+      this.gl.bindAttribLocation(this.programID,index,name);
+    };
+    this.SetUniformMat4 = function (name, value) {
+      var list = [];
+      list = value.CopyList();
+      this.gl.uniformMatrix4fv(this.GetUniformLocation(name),false,list);
+      $mod.GLFatal(this.gl,"gl.uniformMatrix4fv");
+    };
+    this.SetUniformVec3 = function (name, value) {
+      this.gl.uniform3f(this.GetUniformLocation(name),value.x,value.y,value.z);
+      $mod.GLFatal(this.gl,"gl.uniform3fv");
+    };
+    this.SetUniformFloat = function (name, value) {
+      this.gl.uniform1f(this.GetUniformLocation(name),value);
+      $mod.GLFatal(this.gl,"gl.uniform1f");
+    };
+    this.GetUniformLocation = function (name) {
+      var Result = null;
+      Result = this.gl.getUniformLocation(this.programID,name);
+      $mod.GLFatal(this.gl,"gl.getUniformLocation");
+      return Result;
+    };
+    this.CreateShader = function (theType, source) {
+      var Result = null;
+      var shader = null;
+      shader = this.gl.createShader(theType);
+      if (shader === null) $impl.Fatal("create shader failed");
+      this.gl.shaderSource(shader,source);
+      this.gl.compileShader(shader);
+      if (this.gl.getShaderParameter(shader,this.gl.COMPILE_STATUS)) {
+        return shader;
+      } else {
+        $impl.Fatal$1(this.gl.getShaderInfoLog(shader));
+      };
+      return Result;
+    };
+  });
+  this.TModelData = function (s) {
+    if (s) {
+      this.verticies = s.verticies;
+      this.indicies = s.indicies;
+      this.floatsPerVertex = s.floatsPerVertex;
+    } else {
+      this.verticies = null;
+      this.indicies = null;
+      this.floatsPerVertex = 0;
+    };
+    this.$equal = function (b) {
+      return (this.verticies === b.verticies) && ((this.indicies === b.indicies) && (this.floatsPerVertex === b.floatsPerVertex));
+    };
+  };
+  this.kModelVertexFloats = (3 + 2) + 3;
+  this.TModelVertex = function (s) {
+    if (s) {
+      this.pos = new pas.GLTypes.TVec3(s.pos);
+      this.texCoord = new pas.GLTypes.TVec2(s.texCoord);
+      this.normal = new pas.GLTypes.TVec3(s.normal);
+    } else {
+      this.pos = new pas.GLTypes.TVec3();
+      this.texCoord = new pas.GLTypes.TVec2();
+      this.normal = new pas.GLTypes.TVec3();
+    };
+    this.$equal = function (b) {
+      return this.pos.$equal(b.pos) && (this.texCoord.$equal(b.texCoord) && this.normal.$equal(b.normal));
+    };
+  };
+  this.ModelVertexAddToArray = function (vertex, list) {
+    list.push(vertex.pos.x);
+    list.push(vertex.pos.y);
+    list.push(vertex.pos.z);
+    list.push(vertex.texCoord.x);
+    list.push(vertex.texCoord.y);
+    list.push(vertex.normal.x);
+    list.push(vertex.normal.y);
+    list.push(vertex.normal.z);
+  };
+  rtl.createClass($mod,"TModel",pas.System.TObject,function () {
+    this.$init = function () {
+      pas.System.TObject.$init.call(this);
+      this.gl = null;
+      this.data = new $mod.TModelData();
+      this.vertexBuffer = null;
+      this.indexBuffer = null;
+    };
+    this.$final = function () {
+      this.gl = undefined;
+      this.data = undefined;
+      this.vertexBuffer = undefined;
+      this.indexBuffer = undefined;
+      pas.System.TObject.$final.call(this);
+    };
+    this.Create$1 = function (context, modelData) {
+      this.gl = context;
+      this.data = new $mod.TModelData(modelData);
+      this.Load();
+    };
+    this.Draw = function () {
+      this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuffer);
+      this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,this.indexBuffer);
+      this.EnableAttributes();
+      this.gl.drawElements(this.gl.TRIANGLES,this.data.indicies.length,this.gl.UNSIGNED_SHORT,0);
+      this.gl.bindBuffer(this.gl.ARRAY_BUFFER,null);
+      this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,null);
+    };
+    this.EnableAttributes = function () {
+      var offset = 0;
+      var stride = 0;
+      offset = 0;
+      stride = this.data.floatsPerVertex * $mod.GLSizeof(WebGLRenderingContext.FLOAT);
+      this.gl.enableVertexAttribArray(0);
+      this.gl.vertexAttribPointer(0,3,this.gl.FLOAT,false,stride,offset);
+      offset += $mod.GLSizeof(WebGLRenderingContext.FLOAT) * 3;
+      this.gl.enableVertexAttribArray(1);
+      this.gl.vertexAttribPointer(1,2,this.gl.FLOAT,false,stride,offset);
+      offset += $mod.GLSizeof(WebGLRenderingContext.FLOAT) * 2;
+      this.gl.enableVertexAttribArray(2);
+      this.gl.vertexAttribPointer(2,3,this.gl.FLOAT,false,stride,offset);
+      offset += $mod.GLSizeof(WebGLRenderingContext.FLOAT) * 3;
+    };
+    this.Load = function () {
+      this.indexBuffer = this.gl.createBuffer();
+      this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,this.indexBuffer);
+      this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER,this.data.indicies,this.gl.STATIC_DRAW);
+      this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,null);
+      this.vertexBuffer = this.gl.createBuffer();
+      this.gl.bindBuffer(this.gl.ARRAY_BUFFER,this.vertexBuffer);
+      this.gl.bufferData(this.gl.ARRAY_BUFFER,this.data.verticies,this.gl.STATIC_DRAW);
+      this.gl.bindBuffer(this.gl.ARRAY_BUFFER,null);
+    };
+  });
+  this.GLSizeof = function (glType) {
+    var Result = 0;
+    var $tmp1 = glType;
+    if (($tmp1 === WebGLRenderingContext.UNSIGNED_BYTE) || ($tmp1 === WebGLRenderingContext.BYTE)) {
+      Result = 1}
+     else if (($tmp1 === WebGLRenderingContext.SHORT) || ($tmp1 === WebGLRenderingContext.UNSIGNED_SHORT)) {
+      Result = 2}
+     else if (($tmp1 === WebGLRenderingContext.INT) || ($tmp1 === WebGLRenderingContext.UNSIGNED_INT)) {
+      Result = 4}
+     else if ($tmp1 === WebGLRenderingContext.FLOAT) {
+      Result = 4}
+     else {
+      $impl.Fatal("GLSizeof type is invalid.");
+    };
+    return Result;
+  };
+  this.GLFatal = function (gl, messageString) {
+    var error = 0;
+    error = gl.getError();
+    if (error !== WebGLRenderingContext.NO_ERROR) {
+      var $tmp1 = error;
+      if ($tmp1 === WebGLRenderingContext.INVALID_VALUE) {
+        messageString = messageString + " (GL_INVALID_VALUE)"}
+       else if ($tmp1 === WebGLRenderingContext.INVALID_OPERATION) {
+        messageString = messageString + " (GL_INVALID_OPERATION)"}
+       else if ($tmp1 === WebGLRenderingContext.INVALID_ENUM) {
+        messageString = messageString + " (GL_INVALID_ENUM)"}
+       else {
+        messageString = (messageString + " ") + pas.SysUtils.IntToStr(error);
+      };
+      $impl.Fatal(messageString);
+    };
+  };
+},null,function () {
+  "use strict";
+  var $mod = this;
+  var $impl = $mod.$impl;
+  $impl.Fatal = function (messageString) {
+    pas.System.Writeln("*** FATAL: ",messageString);
+    throw pas.SysUtils.Exception.$create("Create$1",["FATAL"]);
+  };
+  $impl.Fatal$1 = function (messageString) {
+    pas.System.Writeln("*** FATAL: ",messageString);
+    throw pas.SysUtils.Exception.$create("Create$1",["FATAL"]);
+  };
+});
+rtl.module("Terrain",["System","Noise","Matrix","GLTypes","GLUtils","webgl","JS","math"],function () {
+  "use strict";
+  var $mod = this;
+  rtl.createClass($mod,"TTerrain",pas.System.TObject,function () {
+    this.$init = function () {
+      pas.System.TObject.$init.call(this);
+      this.noise$1 = null;
+      this.gl = null;
+      this.noiseOffset = new pas.GLTypes.TVec2();
+      this.terrainSize = 0;
+      this.terrainResolution = 0;
+      this.heights = null;
+      this.model = null;
+    };
+    this.$final = function () {
+      this.noise$1 = undefined;
+      this.gl = undefined;
+      this.noiseOffset = undefined;
+      this.heights = undefined;
+      this.model = undefined;
+      pas.System.TObject.$final.call(this);
+    };
+    this.Create$2 = function (context, inNoise, size, resolution, offset) {
+      this.gl = context;
+      this.noise$1 = inNoise;
+      this.noiseOffset = new pas.GLTypes.TVec2(offset);
+      this.terrainSize = size;
+      this.terrainResolution = resolution;
+    };
+    this.GetHeightAtPoint = function (x, y) {
+      var Result = 0.0;
+      Result = rtl.getNumber(this.heights.GetValue(x,y));
+      return Result;
+    };
+    this.GetWidth = function () {
+      var Result = 0;
+      Result = this.heights.GetWidth();
+      return Result;
+    };
+    this.GetHeight = function () {
+      var Result = 0;
+      Result = this.heights.GetHeight();
+      return Result;
+    };
+    this.Draw = function () {
+      this.model.Draw();
+    };
+    this.Generate = function () {
+      var vertex = new pas.GLUtils.TModelVertex();
+      var topLeft = 0;
+      var topRight = 0;
+      var bottomLeft = 0;
+      var bottomRight = 0;
+      var x = 0;
+      var y = 0;
+      var gz = 0;
+      var gx = 0;
+      var verticies = null;
+      var indicies = null;
+      var data = new pas.GLUtils.TModelData();
+      if (this.noise$1 === null) this.noise$1 = pas.Noise.TNoise.$create("Create$2",[pas.Noise.RandomNoiseSeed(1).slice(0)]);
+      this.heights = pas.Matrix.TMatrix.$create("Create$1",[this.terrainResolution,this.terrainResolution]);
+      verticies = new Array();
+      indicies = new Array();
+      for (var $l1 = 0, $end2 = this.heights.GetWidth() - 1; $l1 <= $end2; $l1++) {
+        y = $l1;
+        for (var $l3 = 0, $end4 = this.heights.GetHeight() - 1; $l3 <= $end4; $l3++) {
+          x = $l3;
+          vertex.pos.x = (x / (this.heights.GetWidth() - 1)) * this.terrainSize;
+          vertex.pos.y = this.GetHeightForVertex(x,y,pas.System.Trunc(this.noiseOffset.x) + x,pas.System.Trunc(this.noiseOffset.y) + y);
+          vertex.pos.z = (y / (this.heights.GetHeight() - 1)) * this.terrainSize;
+          this.heights.SetValue(x,y,vertex.pos.y);
+          vertex.normal = new pas.GLTypes.TVec3(this.CalculateNormal(x,y,pas.System.Trunc(this.noiseOffset.x) + x,pas.System.Trunc(this.noiseOffset.y) + y));
+          vertex.texCoord.x = x / (this.heights.GetWidth() - 1);
+          vertex.texCoord.y = y / (this.heights.GetHeight() - 1);
+          pas.GLUtils.ModelVertexAddToArray(new pas.GLUtils.TModelVertex(vertex),verticies);
+        };
+      };
+      for (var $l5 = 0, $end6 = this.heights.GetWidth() - 2; $l5 <= $end6; $l5++) {
+        gz = $l5;
+        for (var $l7 = 0, $end8 = this.heights.GetHeight() - 2; $l7 <= $end8; $l7++) {
+          gx = $l7;
+          topLeft = (gz * this.heights.GetWidth()) + gx;
+          topRight = topLeft + 1;
+          bottomLeft = ((gz + 1) * this.heights.GetWidth()) + gx;
+          bottomRight = bottomLeft + 1;
+          indicies.push(topLeft);
+          indicies.push(bottomLeft);
+          indicies.push(topRight);
+          indicies.push(topRight);
+          indicies.push(bottomLeft);
+          indicies.push(bottomRight);
+        };
+      };
+      data.verticies = new Float32Array(verticies);
+      data.indicies = new Uint16Array(indicies);
+      data.floatsPerVertex = 8;
+      this.model = pas.GLUtils.TModel.$create("Create$1",[this.gl,new pas.GLUtils.TModelData(data)]);
+    };
+    this.GetHeightForVertex = function (localX, localY, x, y) {
+      var Result = 0.0;
+      Result = this.noise$1.GetNoise(x,y,this.heights.GetWidth(),this.heights.GetHeight(),4,3);
+      Result = Math.pow(Result,5);
+      Result = (Result * 20) - 6;
+      return Result;
+    };
+    this.CalculateNormal = function (localX, localY, x, y) {
+      var Result = new pas.GLTypes.TVec3();
+      var heightL = 0.0;
+      var heightR = 0.0;
+      var heightD = 0.0;
+      var heightU = 0.0;
+      heightL = this.GetHeightForVertex(localX,localY,x - 1,y);
+      heightR = this.GetHeightForVertex(localX,localY,x + 1,y);
+      heightD = this.GetHeightForVertex(localX,localY,x,y - 1);
+      heightU = this.GetHeightForVertex(localX,localY,x,y + 1);
+      Result = new pas.GLTypes.TVec3(pas.GLTypes.V3(heightL - heightR,2.0,heightD - heightU));
+      Result = new pas.GLTypes.TVec3(pas.GLTypes.Normalize(new pas.GLTypes.TVec3(Result)));
+      return Result;
+    };
+  });
+});
+rtl.module("program",["System","Terrain","Noise","Mat4","GLUtils","GLTypes","SysUtils","browserconsole","Web","webgl","JS","math"],function () {
+  "use strict";
+  var $mod = this;
+  this.gl = null;
+  this.shader = null;
+  this.projTransform = null;
+  this.viewTransform = null;
+  this.modelTransform = null;
+  this.debugConsole = null;
+  this.canvasAnimationHandler = 0;
+  this.maps = null;
+  this.camera = new pas.GLTypes.TVec3();
+  this.lightPosition = new pas.GLTypes.TVec3();
+  this.terrainNoise = null;
+  this.terrainSize = 64 * 3;
+  this.terrainResolution = 128;
+  this.flySpeed = 1.3;
+  this.visiblity = 4;
+  rtl.createClass($mod,"TTilingTerrain",pas.Terrain.TTerrain,function () {
+    this.$init = function () {
+      pas.Terrain.TTerrain.$init.call(this);
+      this.neighbor = null;
+    };
+    this.$final = function () {
+      this.neighbor = undefined;
+      pas.Terrain.TTerrain.$final.call(this);
+    };
+    this.GetHeightForVertex = function (localX, localY, x, y) {
+      var Result = 0.0;
+      if ((localY === 0) && (this.neighbor !== null)) {
+        Result = this.neighbor.GetHeightAtPoint(localX,(localY + this.neighbor.GetWidth()) - 1)}
+       else {
+        Result = this.noise$1.GetNoise(x,y,this.GetWidth(),this.GetHeight(),6,3);
+        Result = Math.pow(Result,9) * 60;
+      };
+      return Result;
+    };
+  });
+  this.DrawCanvas = function () {
+    var terrainCoord = new pas.GLTypes.TVec3();
+    var startIndex = 0;
+    var endIndex = 0;
+    var i = 0;
+    var map = null;
+    $mod.gl.clear($mod.gl.COLOR_BUFFER_BIT + $mod.gl.DEPTH_BUFFER_BIT);
+    $mod.viewTransform = pas.Mat4.TMat4.$create("Identity");
+    $mod.viewTransform = $mod.viewTransform.Multiply(pas.Mat4.TMat4.$create("Translate",[$mod.camera.x,$mod.camera.y,$mod.camera.z]));
+    $mod.shader.SetUniformMat4("viewTransform",$mod.viewTransform);
+    $mod.lightPosition.z += $mod.flySpeed;
+    $mod.shader.SetUniformVec3("lightPosition",new pas.GLTypes.TVec3($mod.lightPosition));
+    $mod.camera.z -= $mod.flySpeed;
+    $mod.camera.y = -($mod.terrainSize / 4) + (Math.sin($mod.camera.z / $mod.terrainSize) * 14);
+    $mod.camera.x = -($mod.terrainSize / 2) + (Math.cos($mod.camera.z / $mod.terrainSize) * 20);
+    terrainCoord = new pas.GLTypes.TVec3(pas.GLTypes.Divide(new pas.GLTypes.TVec3($mod.camera),$mod.terrainSize));
+    endIndex = pas.System.Trunc(Math.abs(terrainCoord.z));
+    startIndex = endIndex - $mod.visiblity;
+    if (startIndex < 0) startIndex = 0;
+    for (var $l1 = startIndex, $end2 = endIndex; $l1 <= $end2; $l1++) {
+      i = $l1;
+      if (($mod.maps.length === 0) || (($mod.maps.length === i) && ($mod.maps[i] == null))) {
+        map = $mod.TTilingTerrain.$create("Create$2",[$mod.gl,$mod.terrainNoise,$mod.terrainSize,$mod.terrainResolution,new pas.GLTypes.TVec2(pas.GLTypes.V2(0,$mod.terrainSize * i))]);
+        if ((i - 1) >= 0) map.neighbor = rtl.getObject($mod.maps[i - 1]);
+        map.Generate();
+        $mod.maps.push(map);
+        if ((startIndex - 1) >= 0) $mod.maps[startIndex - 1] = null;
+      };
+      map = rtl.getObject($mod.maps[i]);
+      $mod.modelTransform = pas.Mat4.TMat4.$create("Identity");
+      $mod.modelTransform = $mod.modelTransform.Multiply(pas.Mat4.TMat4.$create("Translate",[0,0,$mod.terrainSize * i]));
+      $mod.shader.SetUniformMat4("modelTransform",$mod.modelTransform);
+      map.Draw();
+    };
+  };
+  this.AnimateCanvas = function (time) {
+    $mod.DrawCanvas();
+    if ($mod.canvasAnimationHandler !== 0) $mod.canvasAnimationHandler = window.requestAnimationFrame($mod.AnimateCanvas);
+  };
+  this.StartAnimatingCanvas = function () {
+    $mod.canvasAnimationHandler = window.requestAnimationFrame($mod.AnimateCanvas);
+  };
+  this.canvas = null;
+  this.vertexShaderSource = "";
+  this.fragmentShaderSource = "";
+  this.element = null;
+  this.texture = null;
+  $mod.$main = function () {
+    $mod.debugConsole = document.getElementById("debug-console");
+    $mod.canvas = document.createElement("canvas");
+    $mod.canvas.width = 800;
+    $mod.canvas.height = 600;
+    document.body.appendChild($mod.canvas);
+    $mod.gl = $mod.canvas.getContext("webgl");
+    if ($mod.gl === null) {
+      pas.System.Writeln("failed to load webgl!");
+      return;
+    };
+    $mod.vertexShaderSource = document.getElementById("vertex.glsl").textContent;
+    $mod.fragmentShaderSource = document.getElementById("fragment.glsl").textContent;
+    $mod.shader = pas.GLUtils.TShader.$create("Create$1",[$mod.gl,$mod.vertexShaderSource,$mod.fragmentShaderSource]);
+    $mod.shader.Compile();
+    $mod.shader.BindAttribLocation(0,"in_position");
+    $mod.shader.BindAttribLocation(1,"in_texCoord");
+    $mod.shader.BindAttribLocation(2,"in_normal");
+    $mod.shader.Link();
+    $mod.shader.Use();
+    $mod.gl.clearColor(0.9,0.9,0.9,1);
+    $mod.gl.viewport(0,0,$mod.canvas.width,$mod.canvas.height);
+    $mod.gl.clear($mod.gl.COLOR_BUFFER_BIT);
+    $mod.gl.enable($mod.gl.DEPTH_TEST);
+    $mod.gl.enable($mod.gl.BLEND);
+    $mod.gl.enable($mod.gl.CULL_FACE);
+    $mod.gl.cullFace($mod.gl.BACK);
+    $mod.projTransform = pas.Mat4.TMat4.$create("Perspective",[60.0,$mod.canvas.width / $mod.canvas.height,0.1,2000]);
+    $mod.shader.SetUniformMat4("projTransform",$mod.projTransform);
+    $mod.viewTransform = pas.Mat4.TMat4.$create("Identity");
+    $mod.shader.SetUniformMat4("viewTransform",$mod.viewTransform);
+    $mod.shader.SetUniformMat4("inverseViewTransform",$mod.viewTransform.Inverse());
+    $mod.lightPosition = new pas.GLTypes.TVec3(pas.GLTypes.V3(0,$mod.terrainSize / 2,-($mod.terrainSize / 2)));
+    $mod.shader.SetUniformVec3("lightPosition",new pas.GLTypes.TVec3($mod.lightPosition));
+    $mod.shader.SetUniformVec3("lightColor",new pas.GLTypes.TVec3(pas.GLTypes.V3(1,1,1)));
+    $mod.shader.SetUniformFloat("shineDamper",1000);
+    $mod.shader.SetUniformFloat("reflectivity",1);
+    $mod.gl.clear($mod.gl.COLOR_BUFFER_BIT + $mod.gl.DEPTH_BUFFER_BIT);
+    $mod.modelTransform = pas.Mat4.TMat4.$create("Identity");
+    $mod.shader.SetUniformMat4("modelTransform",$mod.modelTransform);
+    $mod.camera.x = -($mod.terrainSize / 2);
+    $mod.camera.y = -($mod.terrainSize / 4);
+    $mod.camera.z = -($mod.terrainSize / 2);
+    $mod.element = document.getElementById("terrain-texture");
+    $mod.texture = $mod.gl.createTexture();
+    $mod.gl.bindTexture($mod.gl.TEXTURE_2D,$mod.texture);
+    $mod.gl.texParameteri($mod.gl.TEXTURE_2D,$mod.gl.TEXTURE_WRAP_S,$mod.gl.CLAMP_TO_EDGE);
+    $mod.gl.texParameteri($mod.gl.TEXTURE_2D,$mod.gl.TEXTURE_WRAP_T,$mod.gl.CLAMP_TO_EDGE);
+    $mod.gl.texParameteri($mod.gl.TEXTURE_2D,$mod.gl.TEXTURE_MIN_FILTER,$mod.gl.LINEAR);
+    $mod.gl.texParameteri($mod.gl.TEXTURE_2D,$mod.gl.TEXTURE_MAG_FILTER,$mod.gl.LINEAR);
+    $mod.gl.texImage2D($mod.gl.TEXTURE_2D,0,$mod.gl.RGBA,$mod.gl.RGBA,$mod.gl.UNSIGNED_BYTE,rtl.getObject($mod.element));
+    $mod.terrainNoise = pas.Noise.TNoise.$create("Create$2",[pas.Noise.RandomNoiseSeed(1).slice(0)]);
+    $mod.maps = new Array();
+    $mod.StartAnimatingCanvas();
+  };
+});

+ 229 - 0
Pas2JS_WebGL_Terrain.pas

@@ -0,0 +1,229 @@
+program Pas2JS_WebGL_Terrain;
+uses
+	Terrain, Noise, Types, Mat4, GLUtils, GLTypes, 
+	SysUtils,
+	BrowserConsole, Web, WebGL, WebGL2, JS, Math;
+
+var
+	gl: TJSWebGLRenderingContext;
+	shader: TShader;
+  projTransform: TMat4;
+  viewTransform: TMat4;
+  modelTransform: TMat4;
+
+ var
+  debugConsole: TJSElement;
+  canvasAnimationHandler: integer = 0;
+
+var
+	maps: TJSArray;
+	camera: TVec3;
+	lightPosition: TVec3;
+  terrainNoise: TNoise;
+	terrainSize: integer = 64 * 3;
+	terrainResolution: integer = 128;
+	flySpeed: single = 1.3;
+	visiblity: integer = 4;
+
+type
+	TTilingTerrain = class (TTerrain)
+		public
+			neighbor: TTilingTerrain;
+		protected
+			function GetHeightForVertex (localX, localY, x, y: integer): TNoiseFloat; override;
+	end;
+
+function TTilingTerrain.GetHeightForVertex (localX, localY, x, y: integer): TNoiseFloat; 
+begin		
+	if (localY = 0) and (neighbor <> nil) then
+		result := neighbor.GetHeightAtPoint(localX, localY + neighbor.GetWidth - 1)
+	else
+		begin
+			result := noise.GetNoise(x, y, GetWidth, GetHeight, 6, 3);
+			result := Power(result, 9) * 60;
+		end;
+end;
+
+procedure DrawCanvas;
+var
+	terrainCoord: TVec3;
+	startIndex, endIndex: integer;
+	i: integer;
+	map: TTilingTerrain;
+	neighbor: TTilingTerrain = nil;
+begin
+	gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT);
+
+	// apply camera to view transform
+	viewTransform := TMat4.Identity;
+	viewTransform := viewTransform.Multiply(TMat4.Translate(camera.x, camera.y, camera.z));
+	shader.SetUniformMat4('viewTransform', viewTransform);
+
+	// move light with camera
+	lightPosition.z += flySpeed;
+	shader.SetUniformVec3('lightPosition', lightPosition);
+
+	// animate camera
+	camera.z -= flySpeed;
+	camera.y := -(terrainSize/4) + Sin(camera.z / terrainSize) * 14;
+	camera.x := -(terrainSize/2) + Cos(camera.z / terrainSize) * 20;
+	terrainCoord := Divide(camera, terrainSize);
+
+	endIndex := Trunc(Abs(terrainCoord.z));
+	startIndex := endIndex - visiblity;
+	if startIndex < 0 then
+		startIndex := 0;
+
+	// https://gamedev.stackexchange.com/questions/23625/how-do-you-generate-tileable-perlin-noise
+
+	//debugConsole.innerHTML := IntToStr(startIndex)+'/'+IntToStr(endIndex) + VecStr(terrainCoord);
+
+	for i := startIndex to endIndex do
+		begin
+			if (maps.length = 0) or ((maps.length = i) and (maps[i] = nil)) then
+				begin
+					map := TTilingTerrain.Create(gl, terrainNoise, terrainSize, terrainResolution, V2(0, terrainSize * i));
+					if (i - 1) >= 0 then
+						map.neighbor := TTilingTerrain(maps[i - 1]);
+					map.Generate;
+
+					// TODO: take the edge and set the heights to same size as the neighbor
+					// we need to do this pre-vertex generation so we need a callback
+					// or interface (which doesn't exist yet)
+
+					maps.push(map);
+
+					// NOTE: does this free memory in JS?
+					if startIndex - 1 >= 0 then
+						maps[startIndex - 1] := nil;
+				end;
+
+			map := TTilingTerrain(maps[i]);
+			modelTransform := TMat4.Identity;
+			modelTransform := modelTransform.Multiply(TMat4.Translate(0, 0, terrainSize * i));
+			shader.SetUniformMat4('modelTransform', modelTransform);
+			map.Draw;
+		end;
+
+	//if abs(terrainCoord.z) > 10 then
+	//	begin
+	//		writeln('cancel animation');
+	//		window.cancelAnimationFrame(canvasAnimationHandler);
+	//		canvasAnimationHandler := 0;
+	//	end;
+end;
+
+procedure AnimateCanvas(time: TDOMHighResTimeStamp);
+begin
+	DrawCanvas;
+
+	if canvasAnimationHandler <> 0 then
+		canvasAnimationHandler := window.requestAnimationFrame(@AnimateCanvas);
+end;
+
+procedure StartAnimatingCanvas;
+begin
+	canvasAnimationHandler := window.requestAnimationFrame(@AnimateCanvas);
+end;
+
+var
+  canvas: TJSHTMLCanvasElement;
+  i: integer;
+  stride: integer;
+  offset: integer;
+  vertexShaderSource: string;
+  fragmentShaderSource: string;
+  buffer: TJSWebGLBuffer;
+  element: TJSElement;
+  texture: TJSWebGLTexture;
+begin
+
+	// add debug status
+	debugConsole := document.getElementById('debug-console');
+
+	// make webgl context
+  canvas := TJSHTMLCanvasElement(document.createElement('canvas'));
+  canvas.width := 800;
+  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);
+
+	// NOTE: we don't need this in WebGL
+	//gl.enable(gl.TEXTURE_2D);
+	gl.enable(gl.DEPTH_TEST);
+	gl.enable(gl.BLEND);
+	gl.Enable(gl.CULL_FACE);
+	gl.CullFace(gl.BACK);
+
+	projTransform := TMat4.Perspective(60.0, canvas.width / canvas.height, 0.1, 2000);
+
+	shader.SetUniformMat4('projTransform', projTransform);
+
+	viewTransform := TMat4.Identity;
+	//viewTransform := viewTransform.Multiply(TMat4.Translate(-10, -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
+	lightPosition := V3(0, terrainSize / 2, -(terrainSize/2));
+	shader.SetUniformVec3('lightPosition', lightPosition);
+	shader.SetUniformVec3('lightColor', V3(1, 1, 1));
+
+	// model material
+	shader.SetUniformFloat('shineDamper', 1000);
+	shader.SetUniformFloat('reflectivity', 1);
+
+	gl.clear(gl.COLOR_BUFFER_BIT + gl.DEPTH_BUFFER_BIT);
+
+	modelTransform := TMat4.Identity;
+	shader.SetUniformMat4('modelTransform', modelTransform);
+
+	camera.x := -(terrainSize/2);
+	camera.y := -(terrainSize/4);
+	camera.z := -(terrainSize/2);
+
+	// load terrain texture from image tag
+	element := document.getElementById('terrain-texture');
+
+	texture := gl.createTexture;
+	gl.bindTexture(gl.TEXTURE_2D, texture);
+	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
+	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
+	gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, TJSTexImageSource(element));
+
+	terrainNoise := TNoise.Create(RandomNoiseSeed(1));
+	maps := TJSArray.new;
+
+	//map.Draw;
+	StartAnimatingCanvas;
+end.

+ 159 - 0
Terrain.pas

@@ -0,0 +1,159 @@
+unit Terrain;
+interface
+uses
+	Noise, Matrix, MemoryBuffer,
+	GLTypes, GLUtils,
+	WebGL, JS,
+	Math;
+	
+type
+	TTerrain = class (TObject)
+		public	
+			constructor Create (context: TJSWebGLRenderingContext; size, resolution: integer); overload;
+			constructor Create (context: TJSWebGLRenderingContext; inNoise: TNoise; size, resolution: integer; offset: TVec2); overload;
+			
+			function GetHeightAtPoint (x, y: integer): single;
+			function GetWidth: integer;
+			function GetHeight: integer;
+
+			procedure Draw;
+			procedure Generate;
+
+		protected
+			noise: TNoise;
+
+			function GetHeightForVertex (localX, localY, x, y: integer): TNoiseFloat; virtual;
+		private
+			gl: TJSWebGLRenderingContext;
+
+			noiseOffset: TVec2;
+			terrainSize: integer;
+			terrainResolution: integer;
+			heights: TMatrix;
+			model: TModel;
+
+			function CalculateNormal (localX, localY, x, y: integer): TVec3;
+	end;
+
+implementation
+
+procedure TTerrain.Draw;
+begin
+	model.Draw;
+end;
+
+function TTerrain.GetWidth: integer;
+begin
+	result := heights.GetWidth;
+end;
+
+function TTerrain.GetHeight: integer;
+begin
+	result := heights.GetHeight;
+end;
+
+function TTerrain.GetHeightAtPoint (x, y: integer): single;
+begin
+	result := TNoiseFloat(heights.GetValue(x, y));
+end;
+
+function TTerrain.GetHeightForVertex (localX, localY, x, y: integer): TNoiseFloat; 
+begin
+	result := noise.GetNoise(x, y, heights.GetWidth, heights.GetHeight, 4, 3);
+	result := Power(result, 5);
+	// terraces
+	//result := Round(result * 24) / 24;
+	result := (result * 20) - 6;	
+end;
+
+function TTerrain.CalculateNormal (localX, localY, x, y: integer): TVec3; 
+var
+	heightL, heightR, heightD, heightU: TNoiseFloat;
+begin
+	heightL := GetHeightForVertex(localX, localY, x-1, y);
+	heightR := GetHeightForVertex(localX, localY, x+1, y);
+	heightD := GetHeightForVertex(localX, localY, x, y-1);
+	heightU := GetHeightForVertex(localX, localY, x, y+1);
+	result := V3(heightL-heightR, 2.0, heightD - heightU);
+	result := Normalize(result);
+end;
+
+procedure TTerrain.Generate;
+var
+	vertex: TModelVertex;
+	topLeft: integer;
+	topRight: integer;
+	bottomLeft: integer;
+	bottomRight: integer;
+	x, y, gz, gx: integer;
+	verticies: TJSArray;
+	indicies: TJSArray;
+	data: TModelData;
+begin
+	if noise = nil then
+		noise := TNoise.Create(RandomNoiseSeed(1));
+	heights := TMatrix.Create(terrainResolution, terrainResolution);
+
+	verticies := TJSArray.new;//TMemoryBuffer.Create(heights.GetWidth * heights.GetHeight * kModelVertexFloats * 4);
+	indicies := TJSArray.new;
+
+	for y := 0 to heights.GetWidth - 1 do
+	for x := 0 to heights.GetHeight - 1 do
+		begin			
+			vertex.pos.x := x/(heights.GetWidth - 1) * terrainSize;
+			vertex.pos.y := GetHeightForVertex(x, y, Trunc(noiseOffset.x) + x, Trunc(noiseOffset.y) + y);
+			vertex.pos.z := y/(heights.GetHeight - 1) * terrainSize;
+
+			heights.SetValue(x, y, vertex.pos.y);
+
+			vertex.normal := CalculateNormal(x, y, Trunc(noiseOffset.x) + x, Trunc(noiseOffset.y) + y);
+			
+			// distribute linearly between 0-1
+			vertex.texCoord.x := x/(heights.GetWidth - 1);
+			vertex.texCoord.y := y/(heights.GetHeight - 1);
+			
+			ModelVertexAddToArray(vertex, verticies);
+		end;
+	//writeln('terrain floats: ', verticies.length);
+
+	for gz := 0 to heights.GetWidth - 2 do
+	for gx := 0 to heights.GetHeight - 2 do
+		begin
+			topLeft := (gz*heights.GetWidth)+gx;
+			topRight := topLeft + 1;
+			bottomLeft := ((gz+1)*heights.GetWidth)+gx;
+			bottomRight := bottomLeft + 1;
+			
+			indicies.push(topLeft);
+			indicies.push(bottomLeft);
+			indicies.push(topRight);
+			indicies.push(topRight);
+			indicies.push(bottomLeft);
+			indicies.push(bottomRight);
+		end;
+
+	data.verticies := TJSFloat32Array.New(TJSObject(verticies));
+	data.indicies := TJSUint16Array.New(TJSObject(indicies));
+	data.floatsPerVertex := kModelVertexFloats;
+
+	model := TModel.Create(gl, data);
+end;
+
+constructor TTerrain.Create (context: TJSWebGLRenderingContext; inNoise: TNoise; size, resolution: integer; offset: TVec2);
+begin
+	gl := context;
+	noise := inNoise;
+	noiseOffset := offset;
+	terrainSize := size;
+	terrainResolution := resolution;
+end;
+
+constructor TTerrain.Create (context: TJSWebGLRenderingContext; size, resolution: integer);
+begin
+	gl := context;
+	noiseOffset := V2(0, 0);
+	terrainSize := size;
+	terrainResolution := resolution;
+end;
+
+end.

+ 96 - 0
html/Pas2JS_WebGL_Terrain.html

@@ -0,0 +1,96 @@
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <script type="application/javascript" src="../Pas2JS_WebGL_Terrain.js"></script>
+  </head>
+  <body>
+    <pre id="debug-console"></pre>
+    <image id="terrain-texture" src="res/ground.jpg" hidden/>
+
+  	<script type="application/glsl" id="vertex.glsl">
+      attribute vec3 in_position;
+      attribute vec2 in_texCoord;
+      attribute vec3 in_normal;
+
+      uniform mat4 projTransform;
+      uniform mat4 viewTransform;
+      uniform mat4 modelTransform;
+      uniform mat4 inverseViewTransform;
+
+      uniform vec3 lightPosition;
+
+      varying vec3 toLight;
+      varying vec3 surfaceNormal;
+      varying vec3 toCamera;
+      varying vec2 vertexTexCoord;
+      varying float visibility;
+
+      // constants
+      const float density = 0.02;
+      const float gradient = 1.5;
+
+      void main() {
+        vec4 worldPosition = modelTransform * vec4(in_position, 1);
+        vec4 positionRelativeToCamera = viewTransform * worldPosition;
+        gl_Position = projTransform * viewTransform * worldPosition;
+        surfaceNormal = (modelTransform * vec4(in_normal, 0)).xyz;
+        toLight = lightPosition - worldPosition.xyz;
+        toCamera = (inverseViewTransform * vec4(0, 0, 0, 1)).xyz - worldPosition.xyz;
+        vertexTexCoord = in_texCoord;
+
+        // fog
+        // TODO: make this relative to some actual distance in glcoords
+        float dist = length(positionRelativeToCamera.xyz) / 6.0;
+        visibility = exp(-pow((dist * density), gradient));
+        visibility = clamp(visibility, 0.0, 1.0);
+      }
+  	</script>
+  	<script type="application/glsl" id="fragment.glsl">
+      precision mediump float;
+
+      uniform vec3 lightColor;
+      uniform float shineDamper;
+      uniform float reflectivity;
+
+      uniform sampler2D sampler;
+
+      varying vec3 toLight;
+      varying vec3 surfaceNormal;
+      varying vec3 toCamera;
+      varying vec2 vertexTexCoord;
+      varying float visibility;
+
+      const vec3 skyColor = vec3(0.9, 0.9, 0.9);
+      const float ambientLight = 0.3;
+
+      void main() {
+
+        vec3 unitToLight = normalize(toLight);
+        vec3 unitSurfaceNormal = normalize(surfaceNormal);
+
+        float brightness = dot(unitToLight, unitSurfaceNormal);
+        brightness = max(brightness, 0.0);
+        vec3 diffuseLight = lightColor * brightness;
+        diffuseLight = max(diffuseLight, ambientLight);
+
+        vec3 lightDirection = -unitToLight;
+        vec3 reflectedDirection = reflect(lightDirection, unitSurfaceNormal);
+        float specular = dot(reflectedDirection, normalize(toCamera));
+        specular = max(specular, 0.0);
+        float damper = pow(specular, shineDamper);
+        vec3 specularColor = damper * reflectivity * lightColor;
+
+        // add color and light
+        vec4 diffuseColor = texture2D(sampler, vertexTexCoord);
+        vec4 finalColor = vec4(diffuseLight, 1) * diffuseColor + vec4(specularColor, 1);
+
+        finalColor = mix(vec4(skyColor, 1.0), finalColor, visibility);
+
+        gl_FragColor = finalColor;
+      }
+  	</script>
+    <script type="application/javascript">
+     rtl.run();
+    </script>
+  </body>
+</html>

二進制
html/res/ground.jpg


二進制
html/res/spacestars.jpg