169 lines
5 KiB
JavaScript
169 lines
5 KiB
JavaScript
|
import { compileProgram, orthographicProjection } from "rkgk/webgl.js";
|
||
|
|
||
|
const linesVertexShader = `#version 300 es
|
||
|
precision highp float;
|
||
|
|
||
|
uniform mat4 u_projection;
|
||
|
uniform vec2 u_translation;
|
||
|
|
||
|
layout (location = 0) in vec2 a_position;
|
||
|
// Instance
|
||
|
layout (location = 1) in vec4 a_line; // (x1, y1, x2, y2)
|
||
|
layout (location = 2) in vec4 a_color;
|
||
|
layout (location = 3) in vec2 a_properties; // (thickness, hardness)
|
||
|
|
||
|
void main() {
|
||
|
float thickness = a_properties.x;
|
||
|
float hardness = a_properties.y;
|
||
|
|
||
|
vec2 xAxis = a_line.zw - a_line.xy;
|
||
|
vec2 direction = normalize(xAxis);
|
||
|
vec2 yAxis = vec2(-direction.y, direction.x) * thickness;
|
||
|
|
||
|
vec2 localPosition = a_line.xy + xAxis * a_position.x + yAxis * a_position.y;
|
||
|
vec4 screenPosition = vec4(localPosition + u_translation, 0.0, 1.0);
|
||
|
vec4 scenePosition = screenPosition * u_projection;
|
||
|
|
||
|
gl_Position = scenePosition;
|
||
|
}
|
||
|
`;
|
||
|
|
||
|
const linesFragmentShader = `#version 300 es
|
||
|
precision highp float;
|
||
|
|
||
|
out vec4 f_color;
|
||
|
|
||
|
void main() {
|
||
|
f_color = vec4(vec3(0.0), 1.0);
|
||
|
}
|
||
|
`;
|
||
|
|
||
|
const linesMaxInstances = 1;
|
||
|
const lineInstanceSize = 12;
|
||
|
const lineDataBufferSize = lineInstanceSize * linesMaxInstances;
|
||
|
|
||
|
export class BrushRenderer {
|
||
|
#translation = { x: 0, y: 0 };
|
||
|
|
||
|
constructor(gl, canvasSource) {
|
||
|
this.gl = gl;
|
||
|
this.canvasSource = canvasSource;
|
||
|
|
||
|
console.group("construct BrushRenderer");
|
||
|
|
||
|
// Lines
|
||
|
|
||
|
let linesProgramId = compileProgram(gl, linesVertexShader, linesFragmentShader);
|
||
|
this.linesProgram = {
|
||
|
id: linesProgramId,
|
||
|
u_projection: gl.getUniformLocation(linesProgramId, "u_projection"),
|
||
|
u_translation: gl.getUniformLocation(linesProgramId, "u_translation"),
|
||
|
};
|
||
|
|
||
|
this.linesVao = gl.createVertexArray();
|
||
|
this.linesVbo = gl.createBuffer();
|
||
|
|
||
|
gl.bindVertexArray(this.linesVao);
|
||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.linesVbo);
|
||
|
|
||
|
const lineRect = new Float32Array(
|
||
|
// prettier-ignore
|
||
|
[
|
||
|
0, -0.5,
|
||
|
1, -0.5,
|
||
|
0, 0.5,
|
||
|
1, -0.5,
|
||
|
1, 0.5,
|
||
|
0, 0.5,
|
||
|
],
|
||
|
);
|
||
|
|
||
|
this.linesVboData = new Float32Array(lineRect.length + lineDataBufferSize);
|
||
|
this.linesVboData.set(lineRect, 0);
|
||
|
this.linesInstanceData = this.linesVboData.subarray(lineRect.length);
|
||
|
|
||
|
gl.bufferData(gl.ARRAY_BUFFER, this.linesVboData, gl.DYNAMIC_DRAW, 0);
|
||
|
|
||
|
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 2 * 4, 0); // a_position
|
||
|
gl.vertexAttribPointer(1, 4, gl.FLOAT, false, lineInstanceSize, lineRect.byteLength); // a_line
|
||
|
gl.vertexAttribPointer(
|
||
|
2, // a_color
|
||
|
4,
|
||
|
gl.FLOAT,
|
||
|
false,
|
||
|
lineInstanceSize,
|
||
|
lineRect.byteLength + 4 * 4,
|
||
|
);
|
||
|
gl.vertexAttribPointer(
|
||
|
3, // a_properties
|
||
|
2,
|
||
|
gl.FLOAT,
|
||
|
false,
|
||
|
lineInstanceSize,
|
||
|
lineRect.byteLength + 4 * 4 * 2,
|
||
|
);
|
||
|
for (let i = 0; i < 4; ++i) gl.enableVertexAttribArray(i);
|
||
|
for (let i = 1; i < 4; ++i) gl.vertexAttribDivisor(i, 1);
|
||
|
|
||
|
console.debug("pipeline lines", {
|
||
|
linesVao: this.linesVao,
|
||
|
linesVbo: this.linesVbo,
|
||
|
linesVboSize: this.linesVboData.byteLength,
|
||
|
linesInstanceDataOffset: this.linesInstanceData.byteOffset,
|
||
|
});
|
||
|
|
||
|
gl.bindVertexArray(null);
|
||
|
|
||
|
console.groupEnd();
|
||
|
}
|
||
|
|
||
|
#drawLines(instanceCount) {
|
||
|
let gl = this.gl;
|
||
|
|
||
|
gl.bindVertexArray(this.linesVao);
|
||
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.linesVbo);
|
||
|
gl.bufferSubData(
|
||
|
gl.ARRAY_BUFFER,
|
||
|
this.linesInstanceData.byteOffset,
|
||
|
this.linesInstanceData.subarray(0, instanceCount * lineInstanceSize),
|
||
|
);
|
||
|
gl.drawArraysInstanced(gl.TRIANGLES, 0, 6, instanceCount);
|
||
|
}
|
||
|
|
||
|
setTranslation(x, y) {
|
||
|
this.#translation.x = x;
|
||
|
this.#translation.y = y;
|
||
|
}
|
||
|
|
||
|
stroke(canvas, r, g, b, a, thickness, x1, y1, x2, y2) {
|
||
|
let gl = this.gl;
|
||
|
|
||
|
let viewport = this.canvasSource.useCanvas(gl, canvas);
|
||
|
|
||
|
gl.useProgram(this.linesProgram.id);
|
||
|
gl.uniformMatrix4fv(
|
||
|
this.linesProgram.u_projection,
|
||
|
false,
|
||
|
orthographicProjection(0, viewport.width, viewport.height, 0, -1, 1),
|
||
|
);
|
||
|
gl.uniform2f(this.linesProgram.u_translation, this.#translation.x, this.#translation.y);
|
||
|
|
||
|
let instances = this.linesInstanceData;
|
||
|
instances[0] = x1;
|
||
|
instances[1] = y1;
|
||
|
instances[2] = x2;
|
||
|
instances[3] = y2;
|
||
|
instances[4] = r / 255;
|
||
|
instances[5] = g / 255;
|
||
|
instances[6] = b / 255;
|
||
|
instances[7] = a / 255;
|
||
|
instances[8] = thickness;
|
||
|
instances[9] = 1; // hardness
|
||
|
this.#drawLines(1);
|
||
|
|
||
|
this.canvasSource.resetCanvas(gl);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
}
|