diff --git a/.gitignore b/.gitignore index 3194a2a..0090cc7 100644 --- a/.gitignore +++ b/.gitignore @@ -173,5 +173,3 @@ dist # Finder (MacOS) folder config .DS_Store - -html \ No newline at end of file diff --git a/generate.ts b/generate.ts index e2a395f..433e247 100644 --- a/generate.ts +++ b/generate.ts @@ -1,6 +1,6 @@ -import indexTemplate from "./generators/index.html.txt"; -import workTemplate from "./generators/work.html.txt"; -import readmeTemplate from "./generators/README.md.txt"; +import indexTemplate from "./templates/index.html.txt"; +import workTemplate from "./templates/work.html.txt"; +import readmeTemplate from "./templates/README.md.txt"; import chalk from "chalk"; export const generate = async (works: string[]) => { diff --git a/html/001-platform-provenance/index.html b/html/001-platform-provenance/index.html new file mode 100644 index 0000000..1974ffa --- /dev/null +++ b/html/001-platform-provenance/index.html @@ -0,0 +1,31 @@ + + +com.mekanoe.art // 001-platform-provenance + + +
+ +
XX.X FPS (XX.X ms)
+
+ + diff --git a/html/001-platform-provenance/main.js b/html/001-platform-provenance/main.js new file mode 100644 index 0000000..d1089af --- /dev/null +++ b/html/001-platform-provenance/main.js @@ -0,0 +1,62 @@ +import { +App, +BasicPlane, +Shader +} from "../chunk-2775206da9dea9ea.js"; + +// src/001-platform-provenance/main.ts +var app2 = new App({ fov: 20 }); +var gl = app2.gl; +var shader2 = new Shader(app2).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, ` + precision highp float; + + uniform float uTime; + uniform float uSinTime; + uniform float uCosTime; + + varying highp vec2 vTextureCoord; + + vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + + float d = q.x - min(q.w, q.y); + float e = 1.0e-10; + return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x); + } + + vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); + } + + void main() { + float zComponent = uSinTime * 0.001 * 0.5 + 0.5; + vec3 hsv = rgb2hsv(vec3(vTextureCoord, zComponent)); + hsv.x += uTime * 0.0001; + hsv.y = 1.0; + hsv.z = 1.0; + vec3 rgb = hsv2rgb(hsv); + + gl_FragColor = vec4(rgb, 1.0); + gl_FragColor = clamp(gl_FragColor, 0.0, 1.0); + } + `).link(); +var plane = new BasicPlane(app2); +plane.attachShader(shader2); +app2.loop(); diff --git a/html/002-enter-the-third/index.html b/html/002-enter-the-third/index.html new file mode 100644 index 0000000..af4fe8d --- /dev/null +++ b/html/002-enter-the-third/index.html @@ -0,0 +1,31 @@ + + +com.mekanoe.art // 002-enter-the-third + + +
+ +
XX.X FPS (XX.X ms)
+
+ + diff --git a/html/002-enter-the-third/main.js b/html/002-enter-the-third/main.js new file mode 100644 index 0000000..3ad13f7 --- /dev/null +++ b/html/002-enter-the-third/main.js @@ -0,0 +1,27 @@ +import { +App, +BasicPlane, +Shader +} from "../chunk-2775206da9dea9ea.js"; + +// src/002-enter-the-third/main.ts +var app2 = new App({ fov: 20 }); +var gl = app2.gl; +var shader2 = new Shader(app2).attach(gl.VERTEX_SHADER, ` + attribute vec4 aVertexPosition; + attribute vec2 aTextureCoord; + + uniform mat4 uModelViewMatrix; + uniform mat4 uProjectionMatrix; + + void main() { + gl_Position = uProjectionMatrix * uModelViewMatrix * aVertexPosition; + } + `).attach(gl.FRAGMENT_SHADER, ` + void main() { + gl_FragColor = vec4(1.0); + } + `).link(); +var plane = new BasicPlane(app2); +plane.attachShader(shader2); +app2.loop(); diff --git a/html/chunk-2775206da9dea9ea.js b/html/chunk-2775206da9dea9ea.js new file mode 100644 index 0000000..29a5d6e --- /dev/null +++ b/html/chunk-2775206da9dea9ea.js @@ -0,0 +1,225 @@ +// src/shader.js +class Shader { + constructor(app) { + this.gl = app.gl; + this.app = app; + this.program = this.gl.createProgram(); + } + 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 time = this.app.now(); + 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(); + } +} + +// src/object.js +class Object { + constructor(app) { + this.gl = app.gl; + this.app = app; + this.vertexPositions = new Float32Array([]); + this.positionBuffer = null; + this.textureBuffer = null; + } + 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); + } +} + +// src/basic-plane.js +class BasicPlane extends Object { + constructor(app) { + super(app); + this.vertexPositions = new Float32Array([ + -1, + -1, + 1, + -1, + -1, + 1, + 1, + 1 + ]); + this.positionBuffer = this.initBuffer(this.vertexPositions); + this.textureBuffer = this.initBuffer(new Float32Array([0, 0, 1, 0, 0, 1, 1, 1])); + this.app.onUpdate(() => this.draw2D()); + } +} + +// src/telemetry.js +class Telemetry { + constructor(app, selector = "#telemetry") { + this.app = app; + this.el = document.querySelector(selector); + if (this.el && location.search.includes("telemetry")) { + this.el.style.display = "block"; + this.app.onAfterUpdate(() => this.onAfterUpdate()); + } + this.frameTimes = []; + this.maxFrameTimes = 100; + this.lastFrameTime = 0; + } + insertTime(time) { + this.frameTimes.push(time); + if (this.frameTimes.length > this.maxFrameTimes) { + this.frameTimes.shift(); + } + } + onAfterUpdate() { + const frameTime = this.app.now() - this.lastFrameTime; + this.insertTime(frameTime); + const averageFrameTime = this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length; + const framesPerSecond = 1000 / averageFrameTime; + this.el.innerHTML = ` + ${framesPerSecond.toFixed(1)} FPS (${averageFrameTime.toFixed(3)} ms)
+ bU: ${this.app.registry.onBeforeUpdate.length} | U: ${this.app.registry.onUpdate.length} | aU: ${this.app.registry.onAfterUpdate.length} + `; + this.lastFrameTime = this.app.now(); + } +} + +// src/app.js +class App { + constructor(config = { + fov: 45 + }) { + this._now = 0; + this.registry = { + onStart: [], + onUpdate: [], + onBeforeUpdate: [], + onAfterUpdate: [] + }; + this.config = config; + this.canvas = document.querySelector("canvas"); + this.canvas.width = window.innerWidth; + this.canvas.height = window.innerHeight; + this.gl = this.canvas.getContext("webgl2"); + 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; + const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight; + const zNear = 0.1; + const zFar = 100; + 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, -6]); + this.projectionMatrix = projectionMatrix; + this.modelViewMatrix = modelViewMatrix; + this.clear(); + this.onBeforeUpdate(() => this.clear()); + this.telemetry = new Telemetry(this); + } + clear() { + const gl = this.gl; + gl.clearColor(0, 0, 0, 1); + gl.clearDepth(1); + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LEQUAL); + 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); + } + onAfterUpdate(fn) { + this.registry.onAfterUpdate.push(fn); + } + start() { + this.registry.onStart.forEach((fn) => fn(this)); + } + update() { + this.registry.onBeforeUpdate.forEach((fn) => fn(this)); + this.registry.onUpdate.forEach((fn) => fn(this)); + this.registry.onAfterUpdate.forEach((fn) => fn(this)); + } + oneShot() { + requestAnimationFrame((now) => { + this._now = now; + this.start(); + this.update(); + }); + } + loop() { + const run = (now) => { + this._now = now; + this.update(); + requestAnimationFrame(run); + }; + requestAnimationFrame(run); + } + now() { + return this._now; + } +} + +export { Shader, BasicPlane, App }; diff --git a/html/index.css b/html/index.css new file mode 100644 index 0000000..d55f26d --- /dev/null +++ b/html/index.css @@ -0,0 +1,52 @@ +* { + box-sizing: border-box; + transition: all 0.2s ease-in-out; +} + +header { + display: flex; + font-size: 2.5rem; + align-items: center; + + & .subtext { + font-size: 1rem; + margin-left: 0.5rem; + color: hsl(39, 68.6%, 31.2%); + & a:hover { + color: hsl(39, 100%, 80%); + } + } +} + +a { + color: inherit; + text-decoration: none; +} + +ul { + list-style: none; + padding: 0; + margin: 0; + padding-left: 3rem; + display: flex; + flex-direction: column; + font-size: 1.5rem; +} + +li { + & a { + text-decoration: underline; + text-decoration-color: transparent; + } + &:hover { + color: hsl(39, 100%, 80%); + & a { + text-decoration-color: inherit; + } + } + + &::before { + content: "▸"; + margin-right: 0.5rem; + } +} \ No newline at end of file diff --git a/html/index.html b/html/index.html new file mode 100644 index 0000000..02ea531 --- /dev/null +++ b/html/index.html @@ -0,0 +1,89 @@ + + +com.mekanoe.art // + + + + +
+
+
com.mekanoe.art //
+
+ << noeidelon >> + [github] + [fedi] +
a collection of 3D works +
+
+
+ +
+
diff --git a/html/work.css b/html/work.css new file mode 100644 index 0000000..cc1fb32 --- /dev/null +++ b/html/work.css @@ -0,0 +1,12 @@ +#telemetry { + position: absolute; + top: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.5); + color: white; + font: monospace; + z-index: 100; + padding: 10px; + font-size: 12px; + display: none; +} \ No newline at end of file diff --git a/generators/README.md.txt b/templates/README.md.txt similarity index 100% rename from generators/README.md.txt rename to templates/README.md.txt diff --git a/generators/index.html.txt b/templates/index.html.txt similarity index 100% rename from generators/index.html.txt rename to templates/index.html.txt diff --git a/generators/work.html.txt b/templates/work.html.txt similarity index 100% rename from generators/work.html.txt rename to templates/work.html.txt