refactor: convert WebGL renderer to TypeScript
This commit is contained in:
+69
-56
@@ -19,9 +19,9 @@
|
||||
return cssColorProbe;
|
||||
}
|
||||
function parseRgbString(value) {
|
||||
const m = /^rgba?\(([^)]+)\)$/.exec(String(value || "").trim());
|
||||
const m = /^rgba?\(([^)]+)\)$/.exec(value.trim());
|
||||
if (!m) return null;
|
||||
const parts = m[1].split(",").map((p) => p.trim());
|
||||
const parts = m[1]?.split(",").map((p) => p.trim()) ?? [];
|
||||
if (parts.length < 3) return null;
|
||||
const r = Number(parts[0]);
|
||||
const g = Number(parts[1]);
|
||||
@@ -36,7 +36,7 @@
|
||||
];
|
||||
}
|
||||
function parseHexColor(value) {
|
||||
const raw = String(value || "").trim();
|
||||
const raw = value.trim();
|
||||
if (!/^#([0-9a-f]{3,8})$/i.test(raw)) return null;
|
||||
let hex = raw.slice(1);
|
||||
if (hex.length === 3 || hex.length === 4) {
|
||||
@@ -50,8 +50,9 @@
|
||||
return [r, g, b, a];
|
||||
}
|
||||
function parseCssColor(value) {
|
||||
const key = String(value ?? "");
|
||||
if (cssColorCache.has(key)) return cssColorCache.get(key).slice();
|
||||
const key = value;
|
||||
const cached = cssColorCache.get(key);
|
||||
if (cached) return [...cached];
|
||||
let parsed = parseHexColor(key) || parseRgbString(key);
|
||||
if (!parsed) {
|
||||
const probe = ensureCssColorProbe();
|
||||
@@ -60,13 +61,13 @@
|
||||
const computed = getComputedStyle(probe).color;
|
||||
parsed = parseRgbString(computed) || [0, 0, 0, 1];
|
||||
}
|
||||
cssColorCache.set(key, parsed.slice());
|
||||
return parsed.slice();
|
||||
cssColorCache.set(key, [...parsed]);
|
||||
return [...parsed];
|
||||
}
|
||||
function hslToRgba(h, s, l, a = 1) {
|
||||
const hue = ((Number(h) || 0) % 360 + 360) % 360 / 360;
|
||||
const sat = Math.max(0, Math.min(1, (Number(s) || 0) / 100));
|
||||
const lig = Math.max(0, Math.min(1, (Number(l) || 0) / 100));
|
||||
const hue = ((h || 0) % 360 + 360) % 360 / 360;
|
||||
const sat = Math.max(0, Math.min(1, (s || 0) / 100));
|
||||
const lig = Math.max(0, Math.min(1, (l || 0) / 100));
|
||||
const q = lig < 0.5 ? lig * (1 + sat) : lig + sat - lig * sat;
|
||||
const p = 2 * lig - q;
|
||||
const hueToRgb = (t) => {
|
||||
@@ -81,28 +82,26 @@
|
||||
const r = sat === 0 ? lig : hueToRgb(hue + 1 / 3);
|
||||
const g = sat === 0 ? lig : hueToRgb(hue);
|
||||
const b = sat === 0 ? lig : hueToRgb(hue - 1 / 3);
|
||||
return [r, g, b, Math.max(0, Math.min(1, Number(a)))];
|
||||
return [r, g, b, Math.max(0, Math.min(1, a))];
|
||||
}
|
||||
function normalizeColor(input, alphaMul = 1) {
|
||||
let rgba;
|
||||
if (Array.isArray(input)) {
|
||||
const arr = input.map((v) => Number(v));
|
||||
const arr = input;
|
||||
if (arr.length >= 4) {
|
||||
rgba = [arr[0], arr[1], arr[2], arr[3]];
|
||||
rgba = [arr[0] ?? 0, arr[1] ?? 0, arr[2] ?? 0, arr[3] ?? 1];
|
||||
} else {
|
||||
rgba = [0, 0, 0, 1];
|
||||
}
|
||||
} else if (typeof input === "string") {
|
||||
rgba = parseCssColor(input);
|
||||
} else if (input && typeof input === "object") {
|
||||
rgba = [
|
||||
Number(input.r) || 0,
|
||||
Number(input.g) || 0,
|
||||
Number(input.b) || 0,
|
||||
Number(input.a ?? 1)
|
||||
];
|
||||
} else {
|
||||
rgba = [0, 0, 0, 1];
|
||||
rgba = [
|
||||
input.r || 0,
|
||||
input.g || 0,
|
||||
input.b || 0,
|
||||
input.a ?? 1
|
||||
];
|
||||
}
|
||||
const out = [
|
||||
Math.max(0, Math.min(1, rgba[0])),
|
||||
@@ -114,6 +113,7 @@
|
||||
}
|
||||
function compileShader(gl, type, source) {
|
||||
const shader = gl.createShader(type);
|
||||
if (shader === null) throw new Error("Unable to create WebGL shader");
|
||||
gl.shaderSource(shader, source);
|
||||
gl.compileShader(shader);
|
||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||
@@ -161,17 +161,28 @@
|
||||
pushColoredVertex(out, dx2, dy2, rgba);
|
||||
}
|
||||
class TrxWebGlRenderer {
|
||||
canvas;
|
||||
options;
|
||||
gl;
|
||||
ready;
|
||||
textures = /* @__PURE__ */ new Map();
|
||||
_colorScratch = new Float32Array(4096 * 6);
|
||||
_colorGpuSize = 0;
|
||||
_texScratch = new Float32Array(6 * 4);
|
||||
colorProgram;
|
||||
colorBuffer;
|
||||
colorLoc;
|
||||
textureProgram;
|
||||
textureBuffer;
|
||||
textureLoc;
|
||||
constructor(canvas, options = {}) {
|
||||
this.canvas = canvas;
|
||||
this.options = { alpha: true, premultipliedAlpha: false, ...options };
|
||||
this.gl = canvas?.getContext("webgl", this.options) || canvas?.getContext("experimental-webgl", this.options) || null;
|
||||
this.gl = canvas.getContext("webgl", this.options) || canvas.getContext("experimental-webgl", this.options);
|
||||
this.ready = !!this.gl;
|
||||
this.textures = /* @__PURE__ */ new Map();
|
||||
this._colorScratch = new Float32Array(4096 * 6);
|
||||
this._colorGpuSize = 0;
|
||||
this._texScratch = new Float32Array(6 * 4);
|
||||
if (!this.ready) return;
|
||||
const gl = this.gl;
|
||||
if (!gl) return;
|
||||
gl.disable(gl.DEPTH_TEST);
|
||||
gl.disable(gl.CULL_FACE);
|
||||
gl.enable(gl.BLEND);
|
||||
@@ -203,7 +214,7 @@
|
||||
};
|
||||
}
|
||||
ensureSize(cssWidth, cssHeight, dpr = window.devicePixelRatio || 1) {
|
||||
if (!this.ready) return false;
|
||||
if (!this.gl) return false;
|
||||
const nextW = Math.max(1, Math.round(cssWidth * dpr));
|
||||
const nextH = Math.max(1, Math.round(cssHeight * dpr));
|
||||
const changed = this.canvas.width !== nextW || this.canvas.height !== nextH;
|
||||
@@ -215,20 +226,22 @@
|
||||
return changed;
|
||||
}
|
||||
clear(color) {
|
||||
if (!this.ready) return;
|
||||
if (!this.gl) return;
|
||||
const gl = this.gl;
|
||||
const rgba = normalizeColor(color);
|
||||
gl.clearColor(rgba[0], rgba[1], rgba[2], rgba[3]);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||
}
|
||||
drawTriangles(vertices) {
|
||||
if (!this.gl) return;
|
||||
this._drawColorGeometry(vertices, this.gl.TRIANGLES);
|
||||
}
|
||||
drawTriangleStrip(vertices) {
|
||||
if (!this.gl) return;
|
||||
this._drawColorGeometry(vertices, this.gl.TRIANGLE_STRIP);
|
||||
}
|
||||
_drawColorGeometry(vertices, mode) {
|
||||
if (!this.ready || !vertices || vertices.length === 0) return;
|
||||
if (!this.gl || vertices.length === 0) return;
|
||||
const gl = this.gl;
|
||||
const count = vertices.length;
|
||||
if (count > this._colorScratch.length) {
|
||||
@@ -263,7 +276,7 @@
|
||||
pushColoredVertex(v, x, y, rgba);
|
||||
pushColoredVertex(v, x + w, y + h, rgba);
|
||||
pushColoredVertex(v, x, y + h, rgba);
|
||||
this._drawColorGeometry(v, this.gl.TRIANGLES);
|
||||
this.drawTriangles(v);
|
||||
}
|
||||
fillGradientRect(x, y, w, h, colorTL, colorTR, colorBR, colorBL) {
|
||||
if (w <= 0 || h <= 0) return;
|
||||
@@ -278,62 +291,62 @@
|
||||
pushColoredVertex(v, x, y, tl);
|
||||
pushColoredVertex(v, x + w, y + h, br);
|
||||
pushColoredVertex(v, x, y + h, bl);
|
||||
this._drawColorGeometry(v, this.gl.TRIANGLES);
|
||||
this.drawTriangles(v);
|
||||
}
|
||||
drawPolyline(points, color, width = 1) {
|
||||
if (!Array.isArray(points) || points.length < 4) return;
|
||||
const rgba = normalizeColor(color);
|
||||
const halfW = Math.max(0.5, Number(width) || 1) / 2;
|
||||
const halfW = Math.max(0.5, width || 1) / 2;
|
||||
const verts = [];
|
||||
for (let i = 0; i < points.length - 2; i += 2) {
|
||||
segmentToQuadVertices(
|
||||
verts,
|
||||
points[i],
|
||||
points[i + 1],
|
||||
points[i + 2],
|
||||
points[i + 3],
|
||||
points[i] ?? 0,
|
||||
points[i + 1] ?? 0,
|
||||
points[i + 2] ?? 0,
|
||||
points[i + 3] ?? 0,
|
||||
halfW,
|
||||
rgba
|
||||
);
|
||||
}
|
||||
this._drawColorGeometry(verts, this.gl.TRIANGLES);
|
||||
this.drawTriangles(verts);
|
||||
}
|
||||
drawSegments(segments, color, width = 1) {
|
||||
if (!Array.isArray(segments) || segments.length < 4) return;
|
||||
const rgba = normalizeColor(color);
|
||||
const halfW = Math.max(0.5, Number(width) || 1) / 2;
|
||||
const halfW = Math.max(0.5, width || 1) / 2;
|
||||
const verts = [];
|
||||
for (let i = 0; i < segments.length - 3; i += 4) {
|
||||
segmentToQuadVertices(
|
||||
verts,
|
||||
segments[i],
|
||||
segments[i + 1],
|
||||
segments[i + 2],
|
||||
segments[i + 3],
|
||||
segments[i] ?? 0,
|
||||
segments[i + 1] ?? 0,
|
||||
segments[i + 2] ?? 0,
|
||||
segments[i + 3] ?? 0,
|
||||
halfW,
|
||||
rgba
|
||||
);
|
||||
}
|
||||
this._drawColorGeometry(verts, this.gl.TRIANGLES);
|
||||
this.drawTriangles(verts);
|
||||
}
|
||||
drawFilledArea(points, baselineY, color) {
|
||||
if (!Array.isArray(points) || points.length < 4) return;
|
||||
const rgba = normalizeColor(color);
|
||||
const verts = [];
|
||||
for (let i = 0; i < points.length; i += 2) {
|
||||
pushColoredVertex(verts, points[i], baselineY, rgba);
|
||||
pushColoredVertex(verts, points[i], points[i + 1], rgba);
|
||||
pushColoredVertex(verts, points[i] ?? 0, baselineY, rgba);
|
||||
pushColoredVertex(verts, points[i] ?? 0, points[i + 1] ?? 0, rgba);
|
||||
}
|
||||
this._drawColorGeometry(verts, this.gl.TRIANGLE_STRIP);
|
||||
this.drawTriangleStrip(verts);
|
||||
}
|
||||
drawPoints(points, size, color) {
|
||||
if (!Array.isArray(points) || points.length < 2) return;
|
||||
const radius = Math.max(1, Number(size) || 1);
|
||||
const radius = Math.max(1, size || 1);
|
||||
const rgba = normalizeColor(color);
|
||||
const verts = [];
|
||||
for (let i = 0; i < points.length; i += 2) {
|
||||
const x = points[i] - radius;
|
||||
const y = points[i + 1] - radius;
|
||||
const x = (points[i] ?? 0) - radius;
|
||||
const y = (points[i + 1] ?? 0) - radius;
|
||||
const w = radius * 2;
|
||||
const h = radius * 2;
|
||||
pushColoredVertex(verts, x, y, rgba);
|
||||
@@ -343,11 +356,11 @@
|
||||
pushColoredVertex(verts, x + w, y + h, rgba);
|
||||
pushColoredVertex(verts, x, y + h, rgba);
|
||||
}
|
||||
this._drawColorGeometry(verts, this.gl.TRIANGLES);
|
||||
this.drawTriangles(verts);
|
||||
}
|
||||
drawDashedVerticalLine(x, y0, y1, dashLen, gapLen, color, width = 1) {
|
||||
const dash = Math.max(1, Number(dashLen) || 1);
|
||||
const gap = Math.max(1, Number(gapLen) || 1);
|
||||
const dash = Math.max(1, dashLen || 1);
|
||||
const gap = Math.max(1, gapLen || 1);
|
||||
const top = Math.min(y0, y1);
|
||||
const bottom = Math.max(y0, y1);
|
||||
const segments = [];
|
||||
@@ -358,7 +371,7 @@
|
||||
this.drawSegments(segments, color, width);
|
||||
}
|
||||
uploadRgbaTexture(name, width, height, data, filter = "linear") {
|
||||
if (!this.ready || !name || !data) return null;
|
||||
if (!this.gl || !name) return null;
|
||||
const gl = this.gl;
|
||||
let entry = this.textures.get(name);
|
||||
if (!entry) {
|
||||
@@ -403,7 +416,7 @@
|
||||
return entry.texture;
|
||||
}
|
||||
drawTexture(name, x, y, w, h, alpha = 1, flipY = true) {
|
||||
if (!this.ready || !name || w <= 0 || h <= 0) return;
|
||||
if (!this.gl || !name || w <= 0 || h <= 0) return;
|
||||
const entry = this.textures.get(name);
|
||||
if (!entry) return;
|
||||
const gl = this.gl;
|
||||
@@ -468,14 +481,14 @@
|
||||
gl.enableVertexAttribArray(this.textureLoc.uv);
|
||||
gl.vertexAttribPointer(this.textureLoc.uv, 2, gl.FLOAT, false, 16, 8);
|
||||
gl.uniform2f(this.textureLoc.resolution, this.canvas.width, this.canvas.height);
|
||||
gl.uniform1f(this.textureLoc.alpha, Math.max(0, Math.min(1, Number(alpha) || 0)));
|
||||
gl.uniform1f(this.textureLoc.alpha, Math.max(0, Math.min(1, alpha || 0)));
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, entry.texture);
|
||||
gl.uniform1i(this.textureLoc.tex, 0);
|
||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||
}
|
||||
}
|
||||
function createRenderer(canvas, options) {
|
||||
function createRenderer(canvas, options = {}) {
|
||||
return new TrxWebGlRenderer(canvas, options);
|
||||
}
|
||||
global.trxParseCssColor = parseCssColor;
|
||||
|
||||
Reference in New Issue
Block a user