<wc-geo-gl></wc-geo-gl>
function getProjectionMatrix(screenHeight, screenWidth, fieldOfView, zNear, zFar){
const aspectRatio = screenHeight / screenWidth;
const fieldOfViewRadians = fieldOfView * (Math.PI / 180);
const fovRatio = 1 / Math.tan(fieldOfViewRadians / 2);
return [
aspectRatio * fovRatio, 0 , 0 , 0,
0 , fovRatio, 0 , 0,
0 , 0 , zFar/(zFar - zNear) , 1,
0 , 0 , (-zFar * zNear)/(zFar - zNear), 0
];
}
function compileShader(context, text, type){
const shader = context.createShader(type);
context.shaderSource(shader, text);
context.compileShader(shader);
if (!context.getShaderParameter(shader, context.COMPILE_STATUS)) {
throw new Error(`Failed to compile shader: ${context.getShaderInfoLog(shader)}`);
}
return shader;
}
function compileProgram(context, vertexShader, fragmentShader){
const program = context.createProgram();
context.attachShader(program, vertexShader);
context.attachShader(program, fragmentShader);
context.linkProgram(program);
if (!context.getProgramParameter(program, context.LINK_STATUS)) {
throw new Error(`Failed to compile WebGL program: ${context.getProgramInfoLog(program)}`);
}
return program;
}
class WcGeoGl extends HTMLElement {
static observedAttributes = ["image", "height", "width"];
#height = 720;
#width = 1280;
#image;
constructor() {
super();
this.bind(this);
}
bind(element) {
element.attachEvents = element.attachEvents.bind(element);
element.cacheDom = element.cacheDom.bind(element);
element.createShadowDom = element.createShadowDom.bind(element);
element.bootGpu = element.bootGpu.bind(element);
element.render = element.render.bind(element);
}
async connectedCallback() {
this.createShadowDom();
this.cacheDom();
this.attachEvents();
await this.bootGpu();
this.render();
}
createShadowDom() {
this.shadow = this.attachShadow({ mode: "open" });
this.shadow.innerHTML = `
<style>
:host { display: block; }
</style>
<canvas width="${this.#width}px" height="${this.#height}px"></canvas>
`;
}
cacheDom() {
this.dom = {};
this.dom.canvas = this.shadow.querySelector("canvas");
}
attachEvents() {
}
async bootGpu() {
this.context = this.dom.canvas.getContext("webgl");
this.program = this.context.createProgram();
const vertexShader = compileShader(this.context, `
uniform mat4 uProjectionMatrix;
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
float angle = -3.1415962 / 4.0;
mat4 rotationY = mat4(
cos(angle), 0, sin(angle), 0,
0, 1, 0, 0,
-sin(angle), 0, cos(angle), 0,
0, 0, 0, 1
);
vec4 translateZ = vec4(0.0, 0.0, 2.0, 0.0);
varying mediump vec4 vColor;
void main(){
gl_Position = uProjectionMatrix * (translateZ + rotationY * vec4(aVertexPosition, 1.0));
vColor = vec4(aVertexColor, 1.0);
}
`, this.context.VERTEX_SHADER);
const fragmentShader = compileShader(this.context, `
varying lowp vec4 vColor;
void main() {
gl_FragColor = vColor;
}
`, this.context.FRAGMENT_SHADER);
this.program = compileProgram(this.context, vertexShader, fragmentShader)
this.context.useProgram(this.program);
this.createPositions();
this.createColors();
//this.createUvs();
this.createIndices();
this.setupUniforms();
//this.createTexture(await loadImage(this.getAttribute("image")));
this.context.enable(this.context.CULL_FACE);
this.context.cullFace(this.context.BACK);
}
createPositions() {
const positionBuffer = this.context.createBuffer();
this.context.bindBuffer(this.context.ARRAY_BUFFER, positionBuffer);
const positions = new Float32Array([
//Front
-0.5, -0.5, -0.5,
0.5, -0.5, -0.5,
0.5, 0.5, -0.5,
-0.5, 0.5, -0.5,
//Back
0.5, -0.5, 0.5,
-0.5, -0.5, 0.5,
-0.5, 0.5, 0.5,
0.5, 0.5, 0.5
]);
this.context.bufferData(this.context.ARRAY_BUFFER, positions, this.context.STATIC_DRAW);
const positionLocation = this.context.getAttribLocation(this.program, "aVertexPosition");
this.context.enableVertexAttribArray(positionLocation);
this.context.vertexAttribPointer(positionLocation, 3, this.context.FLOAT, false, 0, 0);
}
createColors(){
const colorBuffer = this.context.createBuffer();
this.context.bindBuffer(this.context.ARRAY_BUFFER, colorBuffer);
const colors = new Float32Array([
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0,
0.0, 1.0, 0.0
]);
this.context.bufferData(this.context.ARRAY_BUFFER, colors, this.context.STATIC_DRAW);
const vertexColorLocation = this.context.getAttribLocation(this.program, "aVertexColor");
this.context.enableVertexAttribArray(vertexColorLocation);
this.context.vertexAttribPointer(vertexColorLocation, 3, this.context.FLOAT, false, 0, 0);
}
createIndices() {
const indexBuffer = this.context.createBuffer();
this.context.bindBuffer(this.context.ELEMENT_ARRAY_BUFFER, indexBuffer);
const indices = new Uint16Array([
0, 1, 2, //front
0, 2, 3,
1, 4, 7, //right
1, 7, 2,
4, 5, 6, //back
4, 6, 7,
5, 0, 3, //left
5, 3, 6,
3, 2, 7, //top
3, 7, 6,
0, 1, 5, //bottom
1, 4, 5
]);
this.context.bufferData(this.context.ELEMENT_ARRAY_BUFFER, indices, this.context.STATIC_DRAW);
}
setupUniforms(){
const projectionMatrix = new Float32Array(getProjectionMatrix(this.#height, this.#width, 90, 0.01, 100));
const projectionLocation = this.context.getUniformLocation(this.program, "uProjectionMatrix");
this.context.uniformMatrix4fv(projectionLocation, false, projectionMatrix);
}
render() {
this.context.clear(this.context.COLOR_BUFFER_BIT | this.context.DEPTH_BUFFER_BIT);
this.context.drawElements(this.context.TRIANGLES, 24, this.context.UNSIGNED_SHORT, 0);
}
attributeChangedCallback(name, oldValue, newValue) {
if (newValue !== oldValue) {
this[name] = newValue;
}
}
set height(value) {
this.#height = value;
if (this.dom) {
this.dom.canvas.height = value;
}
}
set width(value) {
this.#width = value;
if (this.dom) {
this.dom.canvas.height = value;
}
}
set image(value) {
this.#image = value;
loadImage(value)
.then(img => this.createTexture(img));
}
//TODO: throw away program on detach
}
customElements.define("wc-geo-gl", WcGeoGl);
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.