123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- program Pas2JS_WebGL_Terrain;
- uses
- Terrain, Noise, Types, Mat4, GLUtils, GLTypes,
- SysUtils,
- BrowserConsole, Web, WebGL, 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: TJSFloat32 = 1.3;
- visiblity: integer = 4;
- textureLoaded: boolean = false;
- 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;
- 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);
- shader.SetUniformMat4('inverseViewTransform', viewTransform.Inverse);
- // 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;
- //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;
- 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;
- end;
- procedure AnimateCanvas(time: TJSDOMHighResTimeStamp);
- begin
- if textureLoaded then
- DrawCanvas;
- if canvasAnimationHandler <> 0 then
- canvasAnimationHandler := window.requestAnimationFrame(@AnimateCanvas);
- end;
- procedure StartAnimatingCanvas;
- begin
- canvasAnimationHandler := window.requestAnimationFrame(@AnimateCanvas);
- end;
- function LoadedTexture (event: TEventListenerEvent): boolean;
- var
- texture: TJSWebGLTexture;
- begin
- 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, TexImageSource(event.target));
- textureLoaded := true;
- result := true;
- end;
- var
- canvas: TJSHTMLCanvasElement;
- vertexShaderSource: string;
- fragmentShaderSource: string;
- img: TJSHTMLElement;
- 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.enable(gl.DEPTH_TEST);
- gl.enable(gl.BLEND);
- gl.Enable(gl.CULL_FACE);
- gl.CullFace(gl.BACK);
- // set projection transform
- projTransform := TMat4.Perspective(60.0, canvas.width / canvas.height, 0.1, 2000);
- shader.SetUniformMat4('projTransform', projTransform);
- // 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);
- camera.x := -(terrainSize/2);
- camera.y := -(terrainSize/4);
- camera.z := -(terrainSize/2);
- // load terrain texture from image tag
- //img := TJSHTMLElement(document.getElementById('terrain-texture'));
- // <image id="terrain-texture" crossOrigin="anonymous" src="res/ground.jpg" hidden/>
- img := TJSHTMLElement(document.createElement('IMG'));
- img.setAttribute('height', '512');
- img.setAttribute('width', '512');
- img.setAttribute('crossOrigin', 'anonymous');
- img.setAttribute('src', 'res/ground.jpg');
- img.onload := @LoadedTexture;
- //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(img));
- //textureLoaded := true;
- // TODO: RandSeed doesn't seem to work so we get a random seed each time
- terrainNoise := TNoise.Create(RandomNoiseSeed(1));
- maps := TJSArray.new;
- StartAnimatingCanvas;
- end.
|