Tetrahedra

The code for the tumbling tetrahedra follows the demo. The code is very similar to that for the rotating triangle. We need to draw a triangle for each of the four faces and start with a different vertex each time. We repeat the coordinates and colour of first two vertices in the buffers. In order to zoom in on the tetrahedra, we vary the field of view of the camera with time.

In the second demonstration you can click on the form to rotate a tetrahedron. If you see no display at school, the security system might have blocked it. You can try instead this direct link to the program running on its own page.

Tumbling Tetrahedra

Code of Tetrahedra

unit Form1;

interface

uses
  System.Types, W3C.TypedArray, SmartCL.System, SmartCL.Graphics,
  SmartCL.Controls, SmartCL.Components, SmartCL.Forms, SmartCL.Fonts,
  SmartCL.Borders, SmartCL.Application, Khronos.WebGl, GLS.Base, GLS.Vectors;

type
  TForm1 = class(TW3form)
  protected
    FCanvas: TW3GraphicContext;
    gl: JWebGLRenderingContext;
    rc: TGLRenderingContext;
    FTetBuffer, FColorBuffer: TGLArrayBuffer;
    FFragmentShader: TGLFragmentShader;
    FVertexShader: TGLVertexShader;
    FShaderProgram: TGLShaderProgram;
    FVertexPosAttrib, FVertexColorAttrib: Integer;
    procedure InitializeObject; override;
    procedure SetupScene;
    procedure Render;
  end;

implementation

procedure TForm1.InitializeObject;
begin
  inherited;
  FCanvas := TW3GraphicContext.Create(Self.Handle);
  gl := JWebGLRenderingContext(FCanvas.Handle.getContext('experimental-webgl'));
  rc := TGLRenderingContext.Create;
  rc.GL := gl;
  gl.canvas.width := 500;
  gl.canvas.height := 500;  
  SetupScene;
  Render;
end;

procedure TForm1.SetupScene;
begin
  gl.clearColor(0.0, 0.0, 0.25, 1.0); // Set clear color to blue, fully opaque
  gl.clearDepth(1.0);                 // Clear everything
  gl.enable(gl.DEPTH_TEST);           // Enable depth testing
  gl.depthFunc(gl.LEQUAL);            // Near things obscure far things

  FColorBuffer := TGLArrayBuffer.Create(rc);
  FColorBuffer.SetData([
     1.0, 0.0, 0.0, //r
     0.0, 1.0, 0.0, //g
     0.0, 0.0, 1.0, //b
     1.0, 1.0, 1.0, //w
     1.0, 0.0, 0.0, //repeating the first two colours to match the repeated
     0.0, 1.0, 0.0  //vertices below
    ], abuStatic);

    FTetBuffer := TGLArrayBuffer.Create(rc);

    FTetBuffer.SetData([ //Vertices of tetrahedron are alternate vertices of cube
       -1.0, -1.0,  1.0,
        1.0, -1.0, -1.0,
        1.0,  1.0,  1.0,
       -1.0,  1.0, -1.0,
       -1.0, -1.0,  1.0,  //repeat the first two vertices
        1.0, -1.0, -1.0
         ], abuStatic);
  // create vertex shader
  FVertexShader := TGLVertexShader.Create(rc);
  if not FVertexShader.Compile(#"
    attribute vec3 aVertexPosition;
    attribute vec3 aVertexColor;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying vec4 vColor;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
      vColor = vec4(aVertexColor, 1.0);
    }") then
    raise Exception.Create(FVertexShader.InfoLog);

  // create fragment shader
  FFragmentShader := TGLFragmentShader.Create(rc);
  FFragmentShader.Compile(#"
    precision mediump float;
    varying vec4 vColor;
    void main(void) {
       gl_FragColor = vColor;
    }");

  // create shader program and link shaders
  FShaderProgram := TGLShaderProgram.Create(rc);
  FShaderProgram.Link(FVertexShader, FFragmentShader);

  FVertexPosAttrib := FShaderProgram.AttribLocation("aVertexPosition");
  FVertexColorAttrib := FShaderProgram.AttribLocation("aVertexColor");
  asm
    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       ||
              window.webkitRequestAnimationFrame ||
              window.mozRequestAnimationFrame    ||
              window.oRequestAnimationFrame      ||
              window.msRequestAnimationFrame     ||
              function( callback ){
                window.setTimeout(callback, 1000 / 60);
              };
    })();
  end;
end;

procedure TForm1.Render;
const
  cSpeed = 24 * 3600;
var
  ProjectionMatrix, ModelViewMatrix : Matrix4;
  ModelViewStack : array of Matrix4;
  timeVar, cPos : Double;

  procedure RenderTetrahedron(x, y, z, rotation : Double);
  begin
    ModelViewStack.Push(ModelViewMatrix); //save the matrix
    ModelViewMatrix := ModelViewMatrix.Translate([x, y, z]);
    ModelViewMatrix := ModelViewMatrix.RotateY(rotation);
    FShaderProgram.SetUniform('uModelViewMatrix', ModelViewMatrix);
    FTetBuffer.VertexAttribPointer(FVertexPosAttrib, 3, False, 0, 0);
    FColorBuffer.VertexAttribPointer(FVertexColorAttrib, 3, False, 0, 0);
    //Draw the four faces of the tet starting from each vertex in turn.
    for var vertex := 0 to 3 do
      gl.drawArrays(gl.TRIANGLES, vertex, 3);
    ModelViewMatrix := ModelViewStack.Pop; //restore the matrix
  end;

begin
  // set viewport to bounds of canvas
  gl.ViewportSet(0, 0, FCanvas.Handle.width, FCanvas.Handle.height);
  // clear background
  gl.clear(gl.COLOR_BUFFER_BIT);
  FShaderProgram.Use;
  //Keep the camera position such that the tetrahedra are at a reasonable size
  timeVar := cSpeed * Frac(Now);
  cPos := 170 - (trunc((20 * timeVar)) mod 120);
  ProjectionMatrix := Matrix4.CreatePerspective(cPos, FCanvas.width / FCanvas.height, 0.1, 100);

  ModelViewMatrix := Matrix4.Identity;
  FShaderProgram.SetUniform('uProjectionMatrix', ProjectionMatrix);
  gl.enableVertexAttribArray(FVertexPosAttrib);
  gl.enableVertexAttribArray(FVertexColorAttrib);

  RenderTetrahedron(-1.5, 0, -4, 45 + timeVar); //nested procedure
  RenderTetrahedron(1.5, 0, -4, 135 + timeVar);

  var renderCallback := @Render;
  asm
    window.requestAnimFrame(@renderCallback);
  end;
end;
 
initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);
end.
    

Two other demonstrations are based on this program. One uses OpenGL within PasSFML and the other displays in a TOpenGLControl

Tetrahedron that Rotates with a Click

Again, the code follows the demonstration. If you see no display at school, the security system might have blocked it. You can try instead this direct link to the program running on its own page.
Tetrahedron that rotates with a click

Code of Tetrahedron

unit Form1;

interface

uses
  System.Types, W3C.TypedArray, SmartCL.System, SmartCL.Graphics,
  SmartCL.Controls, SmartCL.Components, SmartCL.Forms, SmartCL.Fonts,
  SmartCL.Borders, SmartCL.Application, Khronos.WebGl, GLS.Base, GLS.Vectors;

type
  TForm1 = class(TW3form)
  protected
    FCanvas: TW3GraphicContext;
    gl: JWebGLRenderingContext;
    rc: TGLRenderingContext;
    yAngle: Double := 0.0;
    FTetBuffer, FColorBuffer: TGLArrayBuffer;
    FFragmentShader: TGLFragmentShader;
    FVertexShader: TGLVertexShader;
    FShaderProgram: TGLShaderProgram;
    FVertexPosAttrib, FVertexColorAttrib: Integer;
    procedure InitializeObject; override;
    procedure SetupScene;
    procedure Render;
    procedure Rotate(sender : TObject);
  end;

implementation

procedure TForm1.InitializeObject;
begin
  inherited;
  FCanvas := TW3GraphicContext.Create(Self.Handle);
  gl := JWebGLRenderingContext(FCanvas.Handle.getContext('experimental-webgl'));
  rc := TGLRenderingContext.Create;
  rc.GL := gl;
  gl.canvas.width := 500;
  gl.canvas.height := 500;
  OnClick := Rotate;
  SetupScene;
  Render;
end;

procedure TForm1.Rotate(sender : TObject);
begin
  yAngle +=  0.1;
end;

procedure TForm1.SetupScene;
begin
  gl.clearColor(0.0, 0.0, 0.25, 1.0); // Set clear color to blue, fully opaque
  gl.clearDepth(1.0);                 // Clear everything
  gl.enable(gl.DEPTH_TEST);           // Enable depth testing
  gl.depthFunc(gl.LEQUAL);            // Near things obscure far things

  FColorBuffer := TGLArrayBuffer.Create(rc);
  FColorBuffer.SetData([
     1.0, 0.0, 0.0, //r
     0.0, 1.0, 0.0, //g
     0.0, 0.0, 1.0, //b
     1.0, 1.0, 1.0, //w
     1.0, 0.0, 0.0, //repeating the first two colours to match the repeated
     0.0, 1.0, 0.0  //vertices below
    ], abuStatic);

  FTetBuffer := TGLArrayBuffer.Create(rc);

  FTetBuffer.SetData([ //Vertices of tetrahedron are alternate vertices of cube
     -1.0, -1.0,  1.0,
      1.0, -1.0, -1.0,
      1.0,  1.0,  1.0,
     -1.0,  1.0, -1.0,
     -1.0, -1.0,  1.0,  //repeat the first two vertices
      1.0, -1.0, -1.0
       ], abuStatic);
  // create vertex shader
  FVertexShader := TGLVertexShader.Create(rc);
  if not FVertexShader.Compile(#"
    attribute vec3 aVertexPosition;
    attribute vec3 aVertexColor;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    varying vec4 vColor;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * vec4(aVertexPosition, 1.0);
      vColor = vec4(aVertexColor, 1.0);
    }") then
    raise Exception.Create(FVertexShader.InfoLog);

  // create fragment shader
  FFragmentShader := TGLFragmentShader.Create(rc);
  FFragmentShader.Compile(#"
    precision mediump float;
    varying vec4 vColor;
    void main(void) {
       gl_FragColor = vColor;
    }");

  // create shader program and link shaders
  FShaderProgram := TGLShaderProgram.Create(rc);
  FShaderProgram.Link(FVertexShader, FFragmentShader);

  FVertexPosAttrib := FShaderProgram.AttribLocation("aVertexPosition");
  FVertexColorAttrib := FShaderProgram.AttribLocation("aVertexColor");
  asm
    window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       ||
              window.webkitRequestAnimationFrame ||
              window.mozRequestAnimationFrame    ||
              window.oRequestAnimationFrame      ||
              window.msRequestAnimationFrame     ||
              function( callback ){
                window.setTimeout(callback, 1000 / 500);
              };
    })();
  end;
end;

procedure TForm1.Render;
var
  ProjectionMatrix, ModelViewMatrix : Matrix4;
  ModelViewStack : array of Matrix4;

  procedure RenderTetrahedron(x, y, z, theta : Double);
  begin
    ModelViewStack.Push(ModelViewMatrix); //save the matrix
    ModelViewMatrix := ModelViewMatrix.Translate([x, y, z]);
    ModelViewMatrix := ModelViewMatrix.RotateY(theta);
    FShaderProgram.SetUniform('uModelViewMatrix', ModelViewMatrix);
    FTetBuffer.VertexAttribPointer(FVertexPosAttrib, 3, False, 0, 0);
    FColorBuffer.VertexAttribPointer(FVertexColorAttrib, 3, False, 0, 0);
    //Draw the four faces of the tet starting from each vertex in turn.
    for var vertex := 0 to 3 do
      gl.drawArrays(gl.TRIANGLES, vertex, 3);
    ModelViewMatrix := ModelViewStack.Pop; //restore the matrix
  end;

begin
  // set viewport to bounds of canvas
  gl.ViewportSet(0, 0, FCanvas.Handle.width, FCanvas.Handle.height);
  // clear background
  gl.clear(gl.COLOR_BUFFER_BIT);
  FShaderProgram.Use;
  ProjectionMatrix := Matrix4.CreatePerspective(45, FCanvas.width / FCanvas.height, 0.1, 100);

  ModelViewMatrix := Matrix4.Identity;
  FShaderProgram.SetUniform('uProjectionMatrix', ProjectionMatrix);
  gl.enableVertexAttribArray(FVertexPosAttrib);
  gl.enableVertexAttribArray(FVertexColorAttrib);

  RenderTetrahedron(0, 0, -4, yAngle); //nested procedure

  var renderCallback := @Render;
  asm
    window.requestAnimFrame(@renderCallback);
  end;
end;

 
initialization
  Forms.RegisterForm({$I %FILE%}, TForm1);

end.    

Programming - a skill for life!

Using WebGL for advanced graphics in Smart Mobile Studio