diff --git a/generators/work.html.template b/generators/work.html.template
index 915b545..eae123a 100644
--- a/generators/work.html.template
+++ b/generators/work.html.template
@@ -2,20 +2,21 @@
com.mekanoe.art // ##name##
-
+
-
-
\ No newline at end of file
+
+
diff --git a/html/001-platform-provenance.html b/html/001-platform-provenance.html
index a465086..d5949c6 100644
--- a/html/001-platform-provenance.html
+++ b/html/001-platform-provenance.html
@@ -2,20 +2,21 @@
com.mekanoe.art // 001-platform-provenance
-
+
-
-
\ No newline at end of file
+
+
diff --git a/html/001-platform-provenance.js b/html/001-platform-provenance.js
index 1790fe9..0ec9e62 100644
--- a/html/001-platform-provenance.js
+++ b/html/001-platform-provenance.js
@@ -1,3 +1,57 @@
-main(() => {
- console.log("Hello, world!");
+import { main } from "./platform.js";
+import { Shader } from "./shader.js";
+import { BasicPlane } from "./basic-plane.js";
+
+main((gl, core) => {
+ const shader = new Shader(gl)
+ .attach(
+ gl.VERTEX_SHADER,
+ `
+ attribute vec4 aVertexPosition;
+ attribute vec2 aTextureCoord;
+
+ uniform mat4 uModelViewMatrix;
+ uniform mat4 uProjectionMatrix;
+
+ varying highp vec2 vTextureCoord;
+
+ void main() {
+ gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition;
+ vTextureCoord = aTextureCoord;
+ }
+ `
+ )
+ .attach(
+ gl.FRAGMENT_SHADER,
+ `
+ uniform lowp float uTime;
+ uniform lowp float uSinTime;
+ uniform lowp float uCosTime;
+
+ varying highp vec2 vTextureCoord;
+
+ void main() {
+ lowp float slowedSinTime = sin(uTime*0.001)*0.5+0.5;
+
+ gl_FragColor = vec4(vTextureCoord, slowedSinTime, 1.0);
+ }
+ `
+ )
+ .link();
+
+ const plane = new BasicPlane(gl, core);
+ plane.attachShader(shader);
+
+ const render = () => {
+ core.clear();
+ plane.draw2D();
+
+ if (gl.getError() !== gl.NO_ERROR) {
+ throw new Error("WebGL error");
+ }
+
+ requestAnimationFrame(render);
+ };
+
+ requestAnimationFrame(render);
});
diff --git a/html/basic-plane.html b/html/basic-plane.html
new file mode 100644
index 0000000..560e4e9
--- /dev/null
+++ b/html/basic-plane.html
@@ -0,0 +1,22 @@
+
+
+com.mekanoe.art // basic-plane
+
+
+
+
+
+
+
diff --git a/html/basic-plane.js b/html/basic-plane.js
new file mode 100644
index 0000000..1555156
--- /dev/null
+++ b/html/basic-plane.js
@@ -0,0 +1,62 @@
+export class BasicPlane {
+ constructor(gl, core) {
+ this.gl = gl;
+ this.core = core;
+
+ this.vertexPositions = new Float32Array([
+ -1.0, -1.0, +1.0, -1.0, -1.0, +1.0, +1.0, +1.0,
+ ]);
+ this.positionBuffer = this.initBuffer(this.vertexPositions);
+ this.textureBuffer = this.initBuffer(
+ new Float32Array([0, 0, 1, 0, 0, 1, 1, 1])
+ );
+ }
+
+ initBuffer(data, draw = this.gl.STATIC_DRAW) {
+ const buffer = this.gl.createBuffer();
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(data), draw);
+
+ return buffer;
+ }
+
+ attachShader(shader) {
+ this.shader = shader;
+ this.vertexPosition = shader.location("aVertexPosition");
+ this.textureCoord = shader.location("aTextureCoord");
+
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
+ this.gl.vertexAttribPointer(
+ this.vertexPosition,
+ 2,
+ this.gl.FLOAT,
+ false,
+ 0,
+ 0
+ );
+ this.gl.enableVertexAttribArray(this.vertexPosition);
+
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.textureBuffer);
+ this.gl.vertexAttribPointer(
+ this.textureCoord,
+ 2,
+ this.gl.FLOAT,
+ false,
+ 0,
+ 0
+ );
+ this.gl.enableVertexAttribArray(this.textureCoord);
+
+ return this;
+ }
+
+ draw2D() {
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
+ this.shader.activate(this.core.projectionMatrix, this.core.modelViewMatrix);
+ this.gl.drawArrays(
+ this.gl.TRIANGLE_STRIP,
+ 0,
+ this.vertexPositions.length / 2
+ );
+ }
+}
diff --git a/html/index.html b/html/index.html
index de18cba..3c75588 100644
--- a/html/index.html
+++ b/html/index.html
@@ -51,7 +51,9 @@
diff --git a/html/platform.js b/html/platform.js
index cf00263..aaf0d48 100644
--- a/html/platform.js
+++ b/html/platform.js
@@ -1,5 +1,11 @@
-function main(next) {
+export const clear = (gl) => {};
+
+export const main = (next) => {
const canvas = document.querySelector("canvas");
+
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
// Initialize the GL context
const gl = canvas.getContext("webgl");
@@ -11,10 +17,34 @@ function main(next) {
return;
}
- // Set clear color to black, fully opaque
- gl.clearColor(0.0, 0.0, 0.0, 1.0);
- // Clear the color buffer with specified clear color
- gl.clear(gl.COLOR_BUFFER_BIT);
+ const core = renderingCore(gl);
+ core.clear();
- next();
-}
+ next(gl, core);
+};
+
+const renderingCore = (gl) => {
+ const fieldOfView = (10 * Math.PI) / 180; // in radians
+ const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+ const zNear = 0.1;
+ const zFar = 100.0;
+ const projectionMatrix = glMatrix.mat4.create();
+ glMatrix.mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
+
+ const modelViewMatrix = glMatrix.mat4.create();
+ glMatrix.mat4.translate(modelViewMatrix, modelViewMatrix, [-0.0, 0.0, -6.0]);
+
+ return {
+ projectionMatrix,
+ modelViewMatrix,
+ clear() {
+ gl.clearColor(0.0, 0.0, 0.0, 1.0); // Clear to black, 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
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ },
+ };
+};
+
+export const glMatrix = window.glMatrix;
diff --git a/html/shader.html b/html/shader.html
new file mode 100644
index 0000000..026da81
--- /dev/null
+++ b/html/shader.html
@@ -0,0 +1,22 @@
+
+
+com.mekanoe.art // shader
+
+
+
+
+
+
+
diff --git a/html/shader.js b/html/shader.js
new file mode 100644
index 0000000..0b945f5
--- /dev/null
+++ b/html/shader.js
@@ -0,0 +1,74 @@
+export class Shader {
+ constructor(gl) {
+ this.gl = gl;
+ this.program = gl.createProgram();
+ this.startTime = Date.now();
+ }
+
+ attach(type, source) {
+ console.log("attaching shader", { type, source });
+ const shader = this.gl.createShader(type);
+
+ this.gl.shaderSource(shader, source);
+ this.gl.compileShader(shader);
+
+ if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
+ throw new Error(
+ "An error occurred compiling the shaders: " +
+ this.gl.getShaderInfoLog(shader)
+ );
+ }
+
+ this.gl.attachShader(this.program, shader);
+
+ return this;
+ }
+
+ link() {
+ this.gl.linkProgram(this.program);
+
+ if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
+ throw new Error(
+ "Unable to initialize the shader program: " +
+ this.gl.getProgramInfoLog(this.program)
+ );
+ }
+
+ console.log("shader linked");
+
+ return this;
+ }
+
+ location(name) {
+ if (name[0] === "a") {
+ return this.gl.getAttribLocation(this.program, name);
+ } else if (name[0] === "u") {
+ return this.gl.getUniformLocation(this.program, name);
+ }
+ }
+
+ updateTime() {
+ const now = Date.now();
+ const time = now - this.startTime;
+ const sinTime = Math.sin(time);
+ const cosTime = Math.cos(time);
+ this.gl.uniform1f(this.location("uTime"), time);
+ this.gl.uniform1f(this.location("uSinTime"), sinTime);
+ this.gl.uniform1f(this.location("uCosTime"), cosTime);
+ }
+
+ activate(projectionMatrix, modelViewMatrix) {
+ this.gl.useProgram(this.program);
+ this.gl.uniformMatrix4fv(
+ this.location("uProjectionMatrix"),
+ false,
+ projectionMatrix
+ );
+ this.gl.uniformMatrix4fv(
+ this.location("uModelViewMatrix"),
+ false,
+ modelViewMatrix
+ );
+ this.updateTime();
+ }
+}