+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
\ No newline at end of file
diff --git a/README.md b/README.md
index 7ccb324..9c60589 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
Foxes dream of electron beams.
+**noe** + **eidelon** _(n. a thing, an image, a reflection)_
+
https://art.mekanoe.com
## Development
@@ -15,3 +17,23 @@ https://art.mekanoe.com
## Artworks
- [./001-platform-provenance](https://art.mekanoe.com/001-platform-provenance)
+
+## Infrastructure
+
+- The "generator" does
+
+ - Generates a work list from .js files within `html`
+ - Creates this README.md file
+ - Creates the index.html file
+ - Creates individual HTML files for each work
+
+- The platform supplies
+ - Shader types
+ - Primitive objects
+ - A canvas to draw on
+ - WebGL hooks
+ - Helpers, etc
+
+## License
+
+Code and images are licensed under the AGPLv3 license. See [LICENSE](./LICENSE) for more information.
diff --git a/bun.lockb b/bun.lockb
index 8547496..c72ae5c 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/generators/README.md.template b/generators/README.md.template
deleted file mode 100644
index 3c0e7c0..0000000
--- a/generators/README.md.template
+++ /dev/null
@@ -1,17 +0,0 @@
-# noeidelon
-
-Foxes dream of electron beams.
-
-https://art.mekanoe.com
-
-## Development
-
-`nix` to install bun... or do it globally.
-
-`bun run .` to generate HTMLs
-
-`bun serve` to serve them locally
-
-## Artworks
-
-
diff --git a/generators/README.md.txt b/generators/README.md.txt
new file mode 100644
index 0000000..64be2ee
--- /dev/null
+++ b/generators/README.md.txt
@@ -0,0 +1,39 @@
+# noeidelon
+
+Foxes dream of electron beams.
+
+**noe** + **eidelon** _(n. a thing, an image, a reflection)_
+
+https://art.mekanoe.com
+
+## Development
+
+`nix` to install bun... or do it globally.
+
+`bun run .` to generate HTMLs
+
+`bun serve` to serve them locally
+
+## Artworks
+
+
+
+## Infrastructure
+
+- The "generator" does
+
+ - Generates a work list from .js files within `html`
+ - Creates this README.md file
+ - Creates the index.html file
+ - Creates individual HTML files for each work
+
+- The platform supplies
+ - Shader types
+ - Primitive objects
+ - A canvas to draw on
+ - WebGL hooks
+ - Helpers, etc
+
+## License
+
+Code and images are licensed under the AGPLv3 license. See [LICENSE](./LICENSE) for more information.
diff --git a/generators/generate.js b/generators/generate.ts
similarity index 76%
rename from generators/generate.js
rename to generators/generate.ts
index 0ee938a..b5022ef 100644
--- a/generators/generate.js
+++ b/generators/generate.ts
@@ -1,5 +1,8 @@
-import { unlinkSync } from "fs";
+import { unlinkSync } from "node:fs";
import { globSync } from "glob";
+import indexTemplate from "./index.html.txt";
+import workTemplate from "./work.html.txt";
+import readmeTemplate from "./README.md.txt";
const allHtmls = globSync("html/*.html").filter(
(file) => file !== "html/index.html"
@@ -8,10 +11,6 @@ for (const htmlFile of allHtmls) {
unlinkSync(htmlFile);
}
-const indexTemplate = await Bun.file("generators/index.html.template").text();
-const workTemplate = await Bun.file("generators/work.html.template").text();
-const readmeTemplate = await Bun.file("generators/README.md.template").text();
-
const allWorks = globSync("html/*.js")
.map((file) => file.replace("html/", "").replace(".js", ""))
.filter((work) => work !== "platform");
@@ -31,7 +30,7 @@ const index = indexTemplate.replace(
await Bun.write("html/index.html", index);
const readme = readmeTemplate.replace(
- //,
+ "",
allWorks
.map((work) => `- [./${work}](https://art.mekanoe.com/${work})`)
.join("\n")
diff --git a/generators/index.html.template b/generators/index.html.txt
similarity index 100%
rename from generators/index.html.template
rename to generators/index.html.txt
diff --git a/generators/work.html.template b/generators/work.html.txt
similarity index 100%
rename from generators/work.html.template
rename to generators/work.html.txt
diff --git a/html/001-platform-provenance.js b/html/001-platform-provenance.js
index e4e6555..8b1623a 100644
--- a/html/001-platform-provenance.js
+++ b/html/001-platform-provenance.js
@@ -1,12 +1,14 @@
-import { main } from "./lib/platform.js";
import { Shader } from "./lib/shader.js";
import { BasicPlane } from "./lib/basic-plane.js";
+import { App } from "./lib/app.js";
-main({ fov: 20 }, (gl, core) => {
- const shader = new Shader(gl, core)
- .attach(
- gl.VERTEX_SHADER,
- `
+const app = new App({ fov: 20 });
+const gl = app.gl;
+
+const shader = new Shader(app)
+ .attach(
+ gl.VERTEX_SHADER,
+ `
attribute vec4 aVertexPosition;
attribute vec2 aTextureCoord;
@@ -20,10 +22,10 @@ main({ fov: 20 }, (gl, core) => {
vTextureCoord = aTextureCoord;
}
`
- )
- .attach(
- gl.FRAGMENT_SHADER,
- `
+ )
+ .attach(
+ gl.FRAGMENT_SHADER,
+ `
uniform lowp float uTime;
uniform lowp float uSinTime;
uniform lowp float uCosTime;
@@ -38,22 +40,10 @@ main({ fov: 20 }, (gl, core) => {
gl_FragColor = clamp(gl_FragColor, 0.0, 1.0);
}
`
- )
- .link();
+ )
+ .link();
- const plane = new BasicPlane(gl, core);
- plane.attachShader(shader);
+const plane = new BasicPlane(app);
+plane.attachShader(shader);
- const render = () => {
- core.clear();
- plane.draw2D();
-
- if (gl.getError() !== gl.NO_ERROR) {
- throw new Error("WebGL error");
- }
-
- requestAnimationFrame(render);
- };
-
- requestAnimationFrame(render);
-});
+app.loop();
diff --git a/html/lib/app.js b/html/lib/app.js
new file mode 100644
index 0000000..a4c8964
--- /dev/null
+++ b/html/lib/app.js
@@ -0,0 +1,92 @@
+export class App {
+ constructor(
+ config = {
+ fov: 45,
+ }
+ ) {
+ this.registry = {
+ onStart: [],
+ onUpdate: [],
+ onBeforeUpdate: [],
+ };
+
+ this.config = config;
+ this.canvas = document.querySelector("canvas");
+ this.canvas.width = window.innerWidth;
+ this.canvas.height = window.innerHeight;
+ this.gl = this.canvas.getContext("webgl");
+
+ if (this.gl === null) {
+ document.querySelector(
+ "main"
+ ).innerHTML = `your browser didn't let me set up webgl
`;
+ throw new Error(
+ "Unable to initialize WebGL. Your browser or machine may not support it."
+ );
+ }
+
+ const gl = this.gl;
+
+ const fieldOfView = (this.config.fov * 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]
+ );
+
+ this.projectionMatrix = projectionMatrix;
+ this.modelViewMatrix = modelViewMatrix;
+
+ this.clear();
+ this.onBeforeUpdate(() => this.clear());
+ }
+
+ clear() {
+ const gl = this.gl;
+
+ 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);
+ }
+
+ onStart(fn) {
+ this.registry.onStart.push(fn);
+ }
+
+ onUpdate(fn) {
+ this.registry.onUpdate.push(fn);
+ }
+
+ onBeforeUpdate(fn) {
+ this.registry.onBeforeUpdate.push(fn);
+ }
+
+ start() {
+ this.registry.onStart.forEach((fn) => fn(this.gl, this));
+ }
+
+ update() {
+ this.registry.onBeforeUpdate.forEach((fn) => fn(this.gl, this));
+ this.registry.onUpdate.forEach((fn) => fn(this.gl, this));
+ }
+
+ loop() {
+ this.update();
+ requestAnimationFrame(() => this.loop());
+ }
+}
diff --git a/html/lib/basic-plane.js b/html/lib/basic-plane.js
index 1555156..34e1a56 100644
--- a/html/lib/basic-plane.js
+++ b/html/lib/basic-plane.js
@@ -1,7 +1,8 @@
-export class BasicPlane {
- constructor(gl, core) {
- this.gl = gl;
- this.core = core;
+import { Object } from "./object.js";
+
+export class BasicPlane extends Object {
+ constructor(app) {
+ super(app);
this.vertexPositions = new Float32Array([
-1.0, -1.0, +1.0, -1.0, -1.0, +1.0, +1.0, +1.0,
@@ -10,53 +11,7 @@ export class BasicPlane {
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
- );
+ this.app.onUpdate(() => this.draw2D());
}
}
diff --git a/html/lib/object.js b/html/lib/object.js
new file mode 100644
index 0000000..1bb25ce
--- /dev/null
+++ b/html/lib/object.js
@@ -0,0 +1,64 @@
+export class Object {
+ constructor(app) {
+ this.gl = app.gl;
+ this.app = app;
+ }
+
+ 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.app.projectionMatrix, this.app.modelViewMatrix);
+ this.gl.drawArrays(
+ this.gl.TRIANGLE_STRIP,
+ 0,
+ this.vertexPositions.length / 2
+ );
+ }
+
+ draw3D() {
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
+ this.shader.activate(this.app.projectionMatrix, this.app.modelViewMatrix);
+ this.gl.drawArrays(
+ this.gl.TRIANGLE_STRIP,
+ 0,
+ this.vertexPositions.length / 3
+ );
+ }
+}
diff --git a/html/lib/platform.js b/html/lib/platform.js
deleted file mode 100644
index e42085f..0000000
--- a/html/lib/platform.js
+++ /dev/null
@@ -1,50 +0,0 @@
-export const clear = (gl) => {};
-
-export const main = (config = { fov: 45 }, next = () => {}) => {
- const canvas = document.querySelector("canvas");
-
- canvas.width = window.innerWidth;
- canvas.height = window.innerHeight;
-
- // Initialize the GL context
- const gl = canvas.getContext("webgl");
-
- // Only continue if WebGL is available and working
- if (gl === null) {
- document.querySelector(
- "main"
- ).innerHTML = `your browser didn't let me set up webgl
`;
- return;
- }
-
- const core = renderingCore(gl, config);
- core.clear();
-
- next(gl, core);
-};
-
-const renderingCore = (gl, config = {}) => {
- const fieldOfView = (config.fov * 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/lib/shader.js b/html/lib/shader.js
index f92e07b..f8af62a 100644
--- a/html/lib/shader.js
+++ b/html/lib/shader.js
@@ -1,8 +1,8 @@
export class Shader {
- constructor(gl, core) {
- this.gl = gl;
- this.core = core;
- this.program = gl.createProgram();
+ constructor(app) {
+ this.gl = app.gl;
+ this.app = app;
+ this.program = this.gl.createProgram();
this.startTime = Date.now();
}
diff --git a/package.json b/package.json
index ee54cb8..23f688b 100644
--- a/package.json
+++ b/package.json
@@ -3,13 +3,18 @@
"type": "module",
"main": "./generators/generate.js",
"scripts": {
- "serve": "serve ./html"
+ "build": "bun ./generators/generate.ts",
+ "build:watch": "bun run build --watch",
+ "serve": "serve ./html",
+ "dev": "run-p serve build:watch"
},
"devDependencies": {
- "bun-types": "latest"
+ "bun-types": "latest",
+ "npm-run-all2": "^6.0.6",
+ "prettier": "^3.0.3"
},
"peerDependencies": {
- "typescript": "^5.0.0"
+ "typescript": "^5.2.2"
},
"dependencies": {
"glob": "^10.3.10",