@viji-dev/core 0.2.10 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1527,6 +1527,1280 @@ class VideoSystem {
1527
1527
  return this.cvSystem.getStats();
1528
1528
  }
1529
1529
  }
1530
+ class P5WorkerAdapter {
1531
+ constructor(offscreenCanvas, _vijiAPI, sceneCode) {
1532
+ this.offscreenCanvas = offscreenCanvas;
1533
+ this.setupFn = sceneCode.setup || null;
1534
+ this.renderFn = sceneCode.render;
1535
+ this.installMinimalShims();
1536
+ }
1537
+ p5Instance = null;
1538
+ setupFn = null;
1539
+ renderFn = null;
1540
+ p5InternalSetupComplete = false;
1541
+ artistSetupComplete = false;
1542
+ p5Class = null;
1543
+ // Cache for converted P5.Image objects
1544
+ imageParameterCache = /* @__PURE__ */ new Map();
1545
+ // Track if P5.js's main canvas has been created
1546
+ mainCanvasCreated = false;
1547
+ /**
1548
+ * Initialize P5 instance after P5.js library is loaded
1549
+ * This must be called after the P5 class is available
1550
+ */
1551
+ async init() {
1552
+ try {
1553
+ const p5Module = await import("https://esm.sh/p5@1.9.4");
1554
+ this.p5Class = p5Module.default || p5Module;
1555
+ const setupPromise = new Promise((resolve) => {
1556
+ new this.p5Class((p) => {
1557
+ this.p5Instance = p;
1558
+ p.setup = () => {
1559
+ p.createCanvas(this.offscreenCanvas.width, this.offscreenCanvas.height);
1560
+ p.noLoop();
1561
+ this.p5InternalSetupComplete = true;
1562
+ resolve();
1563
+ };
1564
+ p.draw = () => {
1565
+ };
1566
+ setTimeout(() => {
1567
+ if (p.setup && typeof p.setup === "function") {
1568
+ try {
1569
+ p.setup();
1570
+ } catch (setupError) {
1571
+ console.error("P5 setup failed:", setupError);
1572
+ resolve();
1573
+ }
1574
+ }
1575
+ }, 0);
1576
+ });
1577
+ });
1578
+ await setupPromise;
1579
+ } catch (error) {
1580
+ console.error("Failed to initialize P5.js:", error);
1581
+ throw error;
1582
+ }
1583
+ }
1584
+ /**
1585
+ * Install minimal DOM shims that P5.js needs for rendering
1586
+ */
1587
+ installMinimalShims() {
1588
+ const self2 = globalThis;
1589
+ if (typeof self2.document === "undefined") {
1590
+ const createStyleProxy = () => new Proxy({}, {
1591
+ get: () => "",
1592
+ set: () => true
1593
+ });
1594
+ const bodyElement = {
1595
+ style: createStyleProxy(),
1596
+ appendChild: () => {
1597
+ },
1598
+ removeChild: () => {
1599
+ },
1600
+ children: [],
1601
+ childNodes: [],
1602
+ firstChild: null,
1603
+ lastChild: null,
1604
+ parentNode: null,
1605
+ ownerDocument: void 0,
1606
+ // Will be set after document is created
1607
+ setAttribute: () => {
1608
+ },
1609
+ getAttribute: () => null,
1610
+ addEventListener: () => {
1611
+ },
1612
+ removeEventListener: () => {
1613
+ },
1614
+ tagName: "BODY"
1615
+ };
1616
+ self2.document = {
1617
+ createElement: (tag) => {
1618
+ if (tag === "canvas") {
1619
+ let canvas;
1620
+ if (!this.mainCanvasCreated) {
1621
+ canvas = this.offscreenCanvas;
1622
+ this.mainCanvasCreated = true;
1623
+ } else {
1624
+ canvas = new OffscreenCanvas(300, 300);
1625
+ }
1626
+ canvas.style = createStyleProxy();
1627
+ canvas.dataset = new Proxy({}, {
1628
+ get: () => void 0,
1629
+ set: () => true
1630
+ });
1631
+ canvas.classList = {
1632
+ add: () => {
1633
+ },
1634
+ remove: () => {
1635
+ },
1636
+ contains: () => false,
1637
+ toggle: () => false
1638
+ };
1639
+ canvas.getBoundingClientRect = () => ({
1640
+ left: 0,
1641
+ top: 0,
1642
+ width: canvas.width,
1643
+ height: canvas.height
1644
+ });
1645
+ return canvas;
1646
+ }
1647
+ return {
1648
+ style: createStyleProxy(),
1649
+ appendChild: () => {
1650
+ },
1651
+ removeChild: () => {
1652
+ },
1653
+ setAttribute: () => {
1654
+ },
1655
+ getAttribute: () => null,
1656
+ tagName: tag.toUpperCase(),
1657
+ addEventListener: () => {
1658
+ },
1659
+ removeEventListener: () => {
1660
+ }
1661
+ };
1662
+ },
1663
+ createElementNS: (_ns, tag) => {
1664
+ return self2.document.createElement(tag);
1665
+ },
1666
+ body: bodyElement,
1667
+ documentElement: {
1668
+ style: createStyleProxy(),
1669
+ children: [],
1670
+ childNodes: []
1671
+ },
1672
+ getElementById: () => null,
1673
+ querySelector: () => null,
1674
+ querySelectorAll: () => [],
1675
+ getElementsByTagName: (tagName) => {
1676
+ if (tagName.toLowerCase() === "main") {
1677
+ return [bodyElement];
1678
+ }
1679
+ return [];
1680
+ },
1681
+ addEventListener: () => {
1682
+ },
1683
+ removeEventListener: () => {
1684
+ },
1685
+ hasFocus: () => true
1686
+ // P5.js checks this for accessibility features
1687
+ };
1688
+ bodyElement.ownerDocument = self2.document;
1689
+ }
1690
+ if (typeof self2.window === "undefined") {
1691
+ self2.window = {
1692
+ devicePixelRatio: 1,
1693
+ innerWidth: this.offscreenCanvas.width,
1694
+ innerHeight: this.offscreenCanvas.height,
1695
+ addEventListener: () => {
1696
+ },
1697
+ removeEventListener: () => {
1698
+ },
1699
+ requestAnimationFrame: (_callback) => {
1700
+ return 0;
1701
+ },
1702
+ cancelAnimationFrame: () => {
1703
+ },
1704
+ setTimeout: self2.setTimeout.bind(self2),
1705
+ clearTimeout: self2.clearTimeout.bind(self2),
1706
+ setInterval: self2.setInterval.bind(self2),
1707
+ clearInterval: self2.clearInterval.bind(self2),
1708
+ performance: self2.performance,
1709
+ console: self2.console,
1710
+ Math: self2.Math,
1711
+ Date: self2.Date,
1712
+ Array: self2.Array,
1713
+ Object: self2.Object
1714
+ };
1715
+ }
1716
+ if (typeof self2.navigator === "undefined") {
1717
+ self2.navigator = {
1718
+ userAgent: "Viji-Worker-P5",
1719
+ platform: "Worker",
1720
+ language: "en-US"
1721
+ };
1722
+ }
1723
+ if (typeof self2.screen === "undefined") {
1724
+ self2.screen = {
1725
+ width: this.offscreenCanvas.width,
1726
+ height: this.offscreenCanvas.height,
1727
+ availWidth: this.offscreenCanvas.width,
1728
+ availHeight: this.offscreenCanvas.height,
1729
+ colorDepth: 24,
1730
+ pixelDepth: 24
1731
+ };
1732
+ }
1733
+ if (typeof self2.HTMLCanvasElement === "undefined") {
1734
+ self2.HTMLCanvasElement = function() {
1735
+ };
1736
+ Object.setPrototypeOf(OffscreenCanvas.prototype, self2.HTMLCanvasElement.prototype);
1737
+ }
1738
+ }
1739
+ /**
1740
+ * Convert ImageBitmap to a P5.js-compatible image object (with caching)
1741
+ * Returns an object that mimics P5.Image structure for P5.js's image() function
1742
+ */
1743
+ getOrCreateP5Image(cacheKey, source) {
1744
+ if (!this.p5Instance) return null;
1745
+ const cached = this.imageParameterCache.get(cacheKey);
1746
+ if (cached && cached.source === source) {
1747
+ return cached.p5Image;
1748
+ }
1749
+ try {
1750
+ const offscreenCanvas = new OffscreenCanvas(source.width, source.height);
1751
+ const ctx = offscreenCanvas.getContext("2d");
1752
+ if (!ctx) {
1753
+ throw new Error("Failed to get 2d context from OffscreenCanvas");
1754
+ }
1755
+ ctx.drawImage(source, 0, 0);
1756
+ const p5ImageWrapper = {
1757
+ canvas: offscreenCanvas,
1758
+ // P5.js looks for img.canvas || img.elt
1759
+ elt: offscreenCanvas,
1760
+ // Fallback for compatibility
1761
+ width: source.width,
1762
+ // Logical width
1763
+ height: source.height
1764
+ // Logical height
1765
+ };
1766
+ this.imageParameterCache.set(cacheKey, { source, p5Image: p5ImageWrapper });
1767
+ return p5ImageWrapper;
1768
+ } catch (error) {
1769
+ console.warn("Failed to convert image to P5-compatible object:", error);
1770
+ return null;
1771
+ }
1772
+ }
1773
+ /**
1774
+ * Add .p5 property to image parameters for P5.js-specific rendering
1775
+ * This allows artists to use p5.image() while keeping .value for native canvas API
1776
+ * @param parameterObjects Map of parameter name to parameter object from ParameterSystem
1777
+ */
1778
+ addP5PropertyToImageParameters(parameterObjects) {
1779
+ if (!this.p5Instance) return;
1780
+ const isImageLike = (value) => {
1781
+ return value instanceof ImageBitmap || value instanceof OffscreenCanvas || value && typeof value === "object" && "width" in value && "height" in value;
1782
+ };
1783
+ for (const [name, param] of parameterObjects) {
1784
+ try {
1785
+ if (param && typeof param === "object" && "value" in param) {
1786
+ const value = param.value;
1787
+ if (value && isImageLike(value) && !("p5" in param)) {
1788
+ Object.defineProperty(param, "p5", {
1789
+ get: () => this.getOrCreateP5Image(name, param.value),
1790
+ enumerable: true,
1791
+ configurable: true
1792
+ });
1793
+ }
1794
+ }
1795
+ } catch (error) {
1796
+ console.warn(`Failed to add .p5 property to parameter '${name}':`, error);
1797
+ continue;
1798
+ }
1799
+ }
1800
+ }
1801
+ /**
1802
+ * Execute one frame of the P5 scene
1803
+ * Called by Viji's render loop
1804
+ * @param vijiAPI The Viji API object passed to artist code
1805
+ * @param parameterObjects Map of parameter objects from ParameterSystem
1806
+ */
1807
+ tick(vijiAPI, parameterObjects) {
1808
+ if (!this.p5Instance || !this.p5InternalSetupComplete) {
1809
+ return;
1810
+ }
1811
+ try {
1812
+ this.addP5PropertyToImageParameters(parameterObjects);
1813
+ if (!this.artistSetupComplete && this.setupFn) {
1814
+ this.setupFn(vijiAPI, this.p5Instance);
1815
+ this.artistSetupComplete = true;
1816
+ }
1817
+ if (this.p5Instance._setProperty) {
1818
+ this.p5Instance._setProperty("frameCount", this.p5Instance.frameCount + 1);
1819
+ }
1820
+ if (this.renderFn) {
1821
+ this.renderFn(vijiAPI, this.p5Instance);
1822
+ }
1823
+ } catch (error) {
1824
+ console.error("P5 render error:", error);
1825
+ throw error;
1826
+ }
1827
+ }
1828
+ /**
1829
+ * Handle canvas resize
1830
+ */
1831
+ resize(width, height) {
1832
+ if (!this.p5Instance) return;
1833
+ this.p5Instance._setProperty("width", width);
1834
+ this.p5Instance._setProperty("height", height);
1835
+ this.p5Instance._setProperty("_width", width);
1836
+ this.p5Instance._setProperty("_height", height);
1837
+ if (this.p5Instance._renderer) {
1838
+ this.p5Instance._renderer.width = width;
1839
+ this.p5Instance._renderer.height = height;
1840
+ }
1841
+ if (typeof this.p5Instance.resizeCanvas === "function") {
1842
+ try {
1843
+ this.p5Instance.resizeCanvas(width, height, true);
1844
+ } catch (error) {
1845
+ console.warn("P5 resize warning:", error);
1846
+ }
1847
+ }
1848
+ }
1849
+ /**
1850
+ * Cleanup P5 instance
1851
+ */
1852
+ destroy() {
1853
+ if (this.p5Instance) {
1854
+ try {
1855
+ if (typeof this.p5Instance.remove === "function") {
1856
+ this.p5Instance.remove();
1857
+ }
1858
+ } catch (error) {
1859
+ console.warn("P5 cleanup warning:", error);
1860
+ }
1861
+ this.p5Instance = null;
1862
+ }
1863
+ this.setupFn = null;
1864
+ this.renderFn = null;
1865
+ this.p5InternalSetupComplete = false;
1866
+ this.artistSetupComplete = false;
1867
+ }
1868
+ }
1869
+ class ShaderParameterParser {
1870
+ /**
1871
+ * Parse all parameter declarations from shader code
1872
+ */
1873
+ static parseParameters(shaderCode) {
1874
+ const parameters = [];
1875
+ const lines = shaderCode.split("\n");
1876
+ for (const line of lines) {
1877
+ const trimmed = line.trim();
1878
+ const match = trimmed.match(/\/\/\s*@viji-(\w+):(\w+)\s+(.+)/);
1879
+ if (match) {
1880
+ const [, type, uniformName, configStr] = match;
1881
+ try {
1882
+ const config = this.parseKeyValuePairs(configStr);
1883
+ const param = {
1884
+ type,
1885
+ uniformName,
1886
+ label: config.label || uniformName,
1887
+ default: config.default,
1888
+ config
1889
+ };
1890
+ this.validateParameter(param);
1891
+ parameters.push(param);
1892
+ } catch (error) {
1893
+ console.warn(`Failed to parse shader parameter: ${line}`, error);
1894
+ }
1895
+ }
1896
+ }
1897
+ return parameters;
1898
+ }
1899
+ /**
1900
+ * Parse key:value pairs from configuration string
1901
+ */
1902
+ static parseKeyValuePairs(configStr) {
1903
+ const config = {};
1904
+ const keyValueRegex = /(\w+):((?:"[^"]*"|\[[^\]]*\]|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|[^\s]+))/g;
1905
+ let match;
1906
+ while ((match = keyValueRegex.exec(configStr)) !== null) {
1907
+ const [, key, value] = match;
1908
+ config[key] = this.parseValue(value);
1909
+ }
1910
+ return config;
1911
+ }
1912
+ /**
1913
+ * Parse individual value from string
1914
+ */
1915
+ static parseValue(value) {
1916
+ if (value.startsWith('"') && value.endsWith('"')) {
1917
+ return value.slice(1, -1);
1918
+ }
1919
+ if (value.startsWith("[") && value.endsWith("]")) {
1920
+ try {
1921
+ return JSON.parse(value);
1922
+ } catch {
1923
+ const items = value.slice(1, -1).split(",").map((s) => s.trim());
1924
+ return items.map((item) => {
1925
+ if (item.startsWith('"') && item.endsWith('"')) {
1926
+ return item.slice(1, -1);
1927
+ }
1928
+ const num2 = parseFloat(item);
1929
+ return isNaN(num2) ? item : num2;
1930
+ });
1931
+ }
1932
+ }
1933
+ if (value.startsWith("#")) {
1934
+ return value;
1935
+ }
1936
+ if (value === "true") return true;
1937
+ if (value === "false") return false;
1938
+ const num = parseFloat(value);
1939
+ if (!isNaN(num)) return num;
1940
+ return value;
1941
+ }
1942
+ /**
1943
+ * Validate parameter definition
1944
+ */
1945
+ static validateParameter(param) {
1946
+ if (!param.type) {
1947
+ throw new Error("Parameter type is required");
1948
+ }
1949
+ if (!param.uniformName) {
1950
+ throw new Error("Parameter uniformName is required");
1951
+ }
1952
+ if (!param.config.label) {
1953
+ throw new Error(`Parameter ${param.uniformName} missing required 'label' key`);
1954
+ }
1955
+ switch (param.type) {
1956
+ case "slider":
1957
+ case "number":
1958
+ if (param.config.default === void 0) {
1959
+ throw new Error(`Parameter ${param.uniformName} of type ${param.type} missing required 'default' key`);
1960
+ }
1961
+ break;
1962
+ case "color":
1963
+ if (param.config.default === void 0) {
1964
+ throw new Error(`Parameter ${param.uniformName} of type 'color' missing required 'default' key`);
1965
+ }
1966
+ if (!param.config.default.startsWith("#")) {
1967
+ throw new Error(`Parameter ${param.uniformName} of type 'color' default must be hex color (e.g., #ff0000)`);
1968
+ }
1969
+ break;
1970
+ case "toggle":
1971
+ if (param.config.default === void 0) {
1972
+ throw new Error(`Parameter ${param.uniformName} of type 'toggle' missing required 'default' key`);
1973
+ }
1974
+ if (typeof param.config.default !== "boolean") {
1975
+ throw new Error(`Parameter ${param.uniformName} of type 'toggle' default must be boolean (true or false)`);
1976
+ }
1977
+ break;
1978
+ case "select":
1979
+ if (param.config.default === void 0) {
1980
+ throw new Error(`Parameter ${param.uniformName} of type 'select' missing required 'default' key`);
1981
+ }
1982
+ if (!param.config.options || !Array.isArray(param.config.options)) {
1983
+ throw new Error(`Parameter ${param.uniformName} of type 'select' missing required 'options' key (array)`);
1984
+ }
1985
+ break;
1986
+ case "image":
1987
+ break;
1988
+ default:
1989
+ console.warn(`Unknown parameter type: ${param.type}`);
1990
+ }
1991
+ if (param.uniformName.startsWith("u_")) {
1992
+ console.warn(`Parameter name "${param.uniformName}" uses reserved prefix "u_". Consider renaming to avoid conflicts with built-in uniforms.`);
1993
+ }
1994
+ }
1995
+ /**
1996
+ * Generate uniform declaration for a parameter
1997
+ */
1998
+ static generateUniformDeclaration(param) {
1999
+ switch (param.type) {
2000
+ case "slider":
2001
+ case "number":
2002
+ return `uniform float ${param.uniformName};`;
2003
+ case "color":
2004
+ return `uniform vec3 ${param.uniformName};`;
2005
+ case "toggle":
2006
+ return `uniform bool ${param.uniformName};`;
2007
+ case "select":
2008
+ return `uniform int ${param.uniformName};`;
2009
+ case "image":
2010
+ return `uniform sampler2D ${param.uniformName};`;
2011
+ default:
2012
+ return `// Unknown parameter type: ${param.type}`;
2013
+ }
2014
+ }
2015
+ }
2016
+ class ShaderWorkerAdapter {
2017
+ constructor(offscreenCanvas, _vijiAPI, shaderCode) {
2018
+ this.shaderCode = shaderCode;
2019
+ this.glslVersion = this.detectGLSLVersion(shaderCode);
2020
+ this.backbufferEnabled = shaderCode.includes("backbuffer");
2021
+ if (this.glslVersion === "glsl300") {
2022
+ const gl = offscreenCanvas.getContext("webgl2");
2023
+ if (!gl) {
2024
+ throw new Error("WebGL 2 not supported. Use GLSL ES 1.00 syntax instead.");
2025
+ }
2026
+ this.gl = gl;
2027
+ } else {
2028
+ const gl = offscreenCanvas.getContext("webgl");
2029
+ if (!gl) {
2030
+ throw new Error("WebGL not supported");
2031
+ }
2032
+ this.gl = gl;
2033
+ }
2034
+ }
2035
+ gl;
2036
+ program = null;
2037
+ uniformLocations = /* @__PURE__ */ new Map();
2038
+ textureUnits = /* @__PURE__ */ new Map();
2039
+ nextTextureUnit = 0;
2040
+ textures = /* @__PURE__ */ new Map();
2041
+ // Fullscreen quad
2042
+ quadBuffer = null;
2043
+ // Parameter definitions
2044
+ parameters = [];
2045
+ // GLSL version detection
2046
+ glslVersion = "glsl100";
2047
+ // Audio FFT texture
2048
+ audioFFTTexture = null;
2049
+ videoTexture = null;
2050
+ segmentationTexture = null;
2051
+ // Backbuffer support (ping-pong framebuffers)
2052
+ backbufferFramebuffer = null;
2053
+ backbufferTexture = null;
2054
+ currentFramebuffer = null;
2055
+ currentTexture = null;
2056
+ backbufferEnabled = false;
2057
+ /**
2058
+ * Initialize the shader adapter
2059
+ */
2060
+ async init() {
2061
+ try {
2062
+ this.parameters = ShaderParameterParser.parseParameters(this.shaderCode);
2063
+ this.createFullscreenQuad();
2064
+ const processedCode = this.injectUniforms(this.shaderCode);
2065
+ this.compileAndLinkShader(processedCode);
2066
+ this.cacheUniformLocations();
2067
+ this.reserveTextureUnits();
2068
+ if (this.backbufferEnabled) {
2069
+ this.createBackbufferFramebuffers();
2070
+ }
2071
+ } catch (error) {
2072
+ console.error("Failed to initialize ShaderWorkerAdapter:", error);
2073
+ throw error;
2074
+ }
2075
+ }
2076
+ /**
2077
+ * Detect GLSL version from shader code
2078
+ */
2079
+ detectGLSLVersion(code) {
2080
+ return code.includes("#version 300") ? "glsl300" : "glsl100";
2081
+ }
2082
+ /**
2083
+ * Create fullscreen quad geometry
2084
+ */
2085
+ createFullscreenQuad() {
2086
+ const vertices = new Float32Array([
2087
+ -1,
2088
+ -1,
2089
+ // Bottom-left
2090
+ 1,
2091
+ -1,
2092
+ // Bottom-right
2093
+ -1,
2094
+ 1,
2095
+ // Top-left
2096
+ 1,
2097
+ 1
2098
+ // Top-right
2099
+ ]);
2100
+ this.quadBuffer = this.gl.createBuffer();
2101
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.quadBuffer);
2102
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, vertices, this.gl.STATIC_DRAW);
2103
+ }
2104
+ /**
2105
+ * Inject built-in and parameter uniforms into shader code
2106
+ */
2107
+ injectUniforms(artistCode) {
2108
+ let versionLine = "";
2109
+ let codeWithoutVersion = artistCode;
2110
+ const lines = artistCode.split("\n");
2111
+ for (let i = 0; i < lines.length; i++) {
2112
+ const trimmed = lines[i].trim();
2113
+ if (trimmed.startsWith("#version")) {
2114
+ versionLine = trimmed;
2115
+ lines[i] = "";
2116
+ codeWithoutVersion = lines.join("\n");
2117
+ break;
2118
+ }
2119
+ }
2120
+ const injectionPoint = this.findInjectionPoint(codeWithoutVersion);
2121
+ const builtInUniforms = this.getBuiltInUniforms();
2122
+ const parameterUniforms = this.parameters.map((p) => ShaderParameterParser.generateUniformDeclaration(p)).join("\n");
2123
+ const usesFwidth = artistCode.includes("fwidth");
2124
+ if (usesFwidth && this.glslVersion === "glsl100") {
2125
+ const ext = this.gl.getExtension("OES_standard_derivatives");
2126
+ if (!ext) {
2127
+ console.warn("Shader uses fwidth() but OES_standard_derivatives extension is not supported. Shader may not compile.");
2128
+ }
2129
+ }
2130
+ const parts = [];
2131
+ if (usesFwidth && this.glslVersion === "glsl100") {
2132
+ parts.push("#extension GL_OES_standard_derivatives : enable");
2133
+ }
2134
+ if (this.glslVersion === "glsl100") {
2135
+ parts.push("");
2136
+ parts.push("#ifdef GL_ES");
2137
+ parts.push("precision mediump float;");
2138
+ parts.push("#endif");
2139
+ } else {
2140
+ parts.push("");
2141
+ parts.push("precision mediump float;");
2142
+ }
2143
+ parts.push("");
2144
+ parts.push("// ===== VIJI AUTO-INJECTED UNIFORMS =====");
2145
+ parts.push("// Built-in uniforms (auto-provided)");
2146
+ parts.push(builtInUniforms);
2147
+ parts.push("");
2148
+ parts.push("// Parameter uniforms (from @viji-* declarations)");
2149
+ parts.push(parameterUniforms);
2150
+ parts.push("");
2151
+ parts.push("// ===== ARTIST CODE =====");
2152
+ const uniformBlock = parts.join("\n");
2153
+ const codeWithUniforms = codeWithoutVersion.slice(0, injectionPoint) + "\n" + uniformBlock + "\n" + codeWithoutVersion.slice(injectionPoint);
2154
+ const finalCode = versionLine ? versionLine + "\n" + codeWithUniforms : codeWithUniforms;
2155
+ console.log("=== INJECTED SHADER CODE (first 50 lines) ===");
2156
+ console.log(finalCode.split("\n").slice(0, 50).join("\n"));
2157
+ console.log("=== END INJECTED CODE ===");
2158
+ return finalCode;
2159
+ }
2160
+ /**
2161
+ * Find where to inject extensions and uniforms
2162
+ * Extensions must come after #version but before any code
2163
+ *
2164
+ * Strategy:
2165
+ * 1. If #version exists, inject right after it
2166
+ * 2. Otherwise, skip ALL comments (single and multi-line) and inject before first code
2167
+ */
2168
+ findInjectionPoint(code) {
2169
+ const lines = code.split("\n");
2170
+ for (let i = 0; i < lines.length; i++) {
2171
+ const line = lines[i].trim();
2172
+ if (line.startsWith("#version")) {
2173
+ return this.getLineEndPosition(code, i);
2174
+ }
2175
+ }
2176
+ let inMultiLineComment = false;
2177
+ let firstCodeLine = 0;
2178
+ for (let i = 0; i < lines.length; i++) {
2179
+ const line = lines[i].trim();
2180
+ if (line.includes("/*")) {
2181
+ inMultiLineComment = true;
2182
+ }
2183
+ if (line.includes("*/")) {
2184
+ inMultiLineComment = false;
2185
+ firstCodeLine = i + 1;
2186
+ continue;
2187
+ }
2188
+ if (inMultiLineComment) {
2189
+ continue;
2190
+ }
2191
+ if (line === "" || line.startsWith("//")) {
2192
+ firstCodeLine = i + 1;
2193
+ continue;
2194
+ }
2195
+ break;
2196
+ }
2197
+ if (firstCodeLine > 0 && firstCodeLine < lines.length) {
2198
+ return this.getLineEndPosition(code, firstCodeLine - 1);
2199
+ }
2200
+ return 0;
2201
+ }
2202
+ /**
2203
+ * Get byte position of end of line N
2204
+ */
2205
+ getLineEndPosition(code, lineNumber) {
2206
+ const lines = code.split("\n");
2207
+ let position = 0;
2208
+ for (let i = 0; i <= lineNumber && i < lines.length; i++) {
2209
+ position += lines[i].length + 1;
2210
+ }
2211
+ return position;
2212
+ }
2213
+ /**
2214
+ * Get built-in uniform declarations
2215
+ */
2216
+ getBuiltInUniforms() {
2217
+ return `// Core - Canvas & Timing
2218
+ uniform vec2 u_resolution;
2219
+ uniform float u_time;
2220
+ uniform float u_deltaTime;
2221
+ uniform int u_frame;
2222
+ uniform float u_pixelRatio;
2223
+ uniform float u_fps;
2224
+
2225
+ // Mouse API
2226
+ uniform vec2 u_mouse;
2227
+ uniform bool u_mouseInCanvas;
2228
+ uniform bool u_mousePressed;
2229
+ uniform bool u_mouseLeft;
2230
+ uniform bool u_mouseRight;
2231
+ uniform bool u_mouseMiddle;
2232
+ uniform vec2 u_mouseVelocity;
2233
+
2234
+ // Keyboard API - Common keys
2235
+ uniform bool u_keySpace;
2236
+ uniform bool u_keyShift;
2237
+ uniform bool u_keyCtrl;
2238
+ uniform bool u_keyAlt;
2239
+ uniform bool u_keyW;
2240
+ uniform bool u_keyA;
2241
+ uniform bool u_keyS;
2242
+ uniform bool u_keyD;
2243
+ uniform bool u_keyUp;
2244
+ uniform bool u_keyDown;
2245
+ uniform bool u_keyLeft;
2246
+ uniform bool u_keyRight;
2247
+
2248
+ // Touch API
2249
+ uniform int u_touchCount;
2250
+ uniform vec2 u_touch0;
2251
+ uniform vec2 u_touch1;
2252
+ uniform vec2 u_touch2;
2253
+ uniform vec2 u_touch3;
2254
+ uniform vec2 u_touch4;
2255
+
2256
+ // Audio
2257
+ uniform float u_audioVolume;
2258
+ uniform float u_audioPeak;
2259
+ uniform float u_audioBass;
2260
+ uniform float u_audioMid;
2261
+ uniform float u_audioTreble;
2262
+ uniform float u_audioSubBass;
2263
+ uniform float u_audioLowMid;
2264
+ uniform float u_audioHighMid;
2265
+ uniform float u_audioPresence;
2266
+ uniform float u_audioBrilliance;
2267
+ uniform sampler2D u_audioFFT;
2268
+
2269
+ // Video
2270
+ uniform sampler2D u_video;
2271
+ uniform vec2 u_videoResolution;
2272
+ uniform float u_videoFrameRate;
2273
+
2274
+ // CV - Face Detection
2275
+ uniform int u_faceCount;
2276
+ uniform vec4 u_face0Bounds;
2277
+ uniform vec3 u_face0HeadPose;
2278
+ uniform float u_face0Confidence;
2279
+ uniform float u_face0Happy;
2280
+ uniform float u_face0Sad;
2281
+ uniform float u_face0Angry;
2282
+ uniform float u_face0Surprised;
2283
+
2284
+ // CV - Hand Tracking
2285
+ uniform int u_handCount;
2286
+ uniform vec3 u_leftHandPalm;
2287
+ uniform vec3 u_rightHandPalm;
2288
+ uniform float u_leftHandFist;
2289
+ uniform float u_leftHandOpen;
2290
+ uniform float u_rightHandFist;
2291
+ uniform float u_rightHandOpen;
2292
+
2293
+ // CV - Pose Detection
2294
+ uniform bool u_poseDetected;
2295
+ uniform vec2 u_nosePosition;
2296
+ uniform vec2 u_leftWristPosition;
2297
+ uniform vec2 u_rightWristPosition;
2298
+ uniform vec2 u_leftAnklePosition;
2299
+ uniform vec2 u_rightAnklePosition;
2300
+
2301
+ // CV - Segmentation
2302
+ uniform sampler2D u_segmentationMask;
2303
+ uniform vec2 u_segmentationRes;
2304
+
2305
+ // Backbuffer (previous frame feedback)
2306
+ ${this.backbufferEnabled ? "uniform sampler2D backbuffer;" : "// backbuffer not enabled"}
2307
+ `;
2308
+ }
2309
+ /**
2310
+ * Compile and link shader program
2311
+ */
2312
+ compileAndLinkShader(fragmentShaderCode) {
2313
+ const gl = this.gl;
2314
+ const vertexShaderCode = this.glslVersion === "glsl300" ? `#version 300 es
2315
+ precision mediump float;
2316
+ in vec2 a_position;
2317
+ void main() {
2318
+ gl_Position = vec4(a_position, 0.0, 1.0);
2319
+ }` : `attribute vec2 a_position;
2320
+ void main() {
2321
+ gl_Position = vec4(a_position, 0.0, 1.0);
2322
+ }`;
2323
+ const vertexShader = this.compileShader(gl.VERTEX_SHADER, vertexShaderCode);
2324
+ const fragmentShader = this.compileShader(gl.FRAGMENT_SHADER, fragmentShaderCode);
2325
+ const program = gl.createProgram();
2326
+ if (!program) {
2327
+ throw new Error("Failed to create WebGL program");
2328
+ }
2329
+ gl.attachShader(program, vertexShader);
2330
+ gl.attachShader(program, fragmentShader);
2331
+ gl.linkProgram(program);
2332
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
2333
+ const error = gl.getProgramInfoLog(program);
2334
+ throw new Error(`Shader program link failed: ${error}`);
2335
+ }
2336
+ this.program = program;
2337
+ gl.useProgram(program);
2338
+ gl.deleteShader(vertexShader);
2339
+ gl.deleteShader(fragmentShader);
2340
+ }
2341
+ /**
2342
+ * Compile a shader
2343
+ */
2344
+ compileShader(type, source) {
2345
+ const gl = this.gl;
2346
+ const shader = gl.createShader(type);
2347
+ if (!shader) {
2348
+ throw new Error("Failed to create shader");
2349
+ }
2350
+ gl.shaderSource(shader, source);
2351
+ gl.compileShader(shader);
2352
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
2353
+ const error = gl.getShaderInfoLog(shader);
2354
+ const shaderType = type === gl.VERTEX_SHADER ? "vertex" : "fragment";
2355
+ throw new Error(`${shaderType} shader compilation failed:
2356
+ ${error}`);
2357
+ }
2358
+ return shader;
2359
+ }
2360
+ /**
2361
+ * Cache uniform locations for fast access
2362
+ */
2363
+ cacheUniformLocations() {
2364
+ if (!this.program) return;
2365
+ const gl = this.gl;
2366
+ const numUniforms = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS);
2367
+ for (let i = 0; i < numUniforms; i++) {
2368
+ const info = gl.getActiveUniform(this.program, i);
2369
+ if (info) {
2370
+ const location = gl.getUniformLocation(this.program, info.name);
2371
+ this.uniformLocations.set(info.name, location);
2372
+ }
2373
+ }
2374
+ }
2375
+ /**
2376
+ * Reserve texture units for special textures
2377
+ */
2378
+ reserveTextureUnits() {
2379
+ this.textureUnits.set("u_audioFFT", this.nextTextureUnit++);
2380
+ this.textureUnits.set("u_video", this.nextTextureUnit++);
2381
+ this.textureUnits.set("u_segmentationMask", this.nextTextureUnit++);
2382
+ if (this.backbufferEnabled) {
2383
+ this.textureUnits.set("backbuffer", this.nextTextureUnit++);
2384
+ }
2385
+ }
2386
+ /**
2387
+ * Create ping-pong framebuffers for backbuffer support
2388
+ */
2389
+ createBackbufferFramebuffers() {
2390
+ const gl = this.gl;
2391
+ const width = gl.canvas.width;
2392
+ const height = gl.canvas.height;
2393
+ const createFBOTexture = () => {
2394
+ const texture = gl.createTexture();
2395
+ gl.bindTexture(gl.TEXTURE_2D, texture);
2396
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
2397
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
2398
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
2399
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
2400
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
2401
+ const framebuffer = gl.createFramebuffer();
2402
+ gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
2403
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
2404
+ return { framebuffer, texture };
2405
+ };
2406
+ const fbo1 = createFBOTexture();
2407
+ const fbo2 = createFBOTexture();
2408
+ this.backbufferFramebuffer = fbo1.framebuffer;
2409
+ this.backbufferTexture = fbo1.texture;
2410
+ this.currentFramebuffer = fbo2.framebuffer;
2411
+ this.currentTexture = fbo2.texture;
2412
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
2413
+ gl.bindTexture(gl.TEXTURE_2D, null);
2414
+ }
2415
+ /**
2416
+ * Main render method
2417
+ */
2418
+ render(viji, parameterObjects) {
2419
+ const gl = this.gl;
2420
+ if (!this.program || !this.quadBuffer) {
2421
+ console.warn("Shader not initialized");
2422
+ return;
2423
+ }
2424
+ gl.useProgram(this.program);
2425
+ this.updateBuiltInUniforms(viji);
2426
+ this.updateParameterUniforms(parameterObjects);
2427
+ if (this.backbufferEnabled && this.backbufferTexture) {
2428
+ const backbufferUnit = this.textureUnits.get("backbuffer");
2429
+ if (backbufferUnit !== void 0) {
2430
+ gl.activeTexture(gl.TEXTURE0 + backbufferUnit);
2431
+ gl.bindTexture(gl.TEXTURE_2D, this.backbufferTexture);
2432
+ this.setUniform("backbuffer", "sampler2D", backbufferUnit);
2433
+ }
2434
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.currentFramebuffer);
2435
+ }
2436
+ const positionLocation = gl.getAttribLocation(this.program, "a_position");
2437
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.quadBuffer);
2438
+ gl.enableVertexAttribArray(positionLocation);
2439
+ gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
2440
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
2441
+ if (this.backbufferEnabled) {
2442
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
2443
+ gl.activeTexture(gl.TEXTURE0);
2444
+ gl.bindTexture(gl.TEXTURE_2D, this.currentTexture);
2445
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
2446
+ const tempFB = this.backbufferFramebuffer;
2447
+ const tempTex = this.backbufferTexture;
2448
+ this.backbufferFramebuffer = this.currentFramebuffer;
2449
+ this.backbufferTexture = this.currentTexture;
2450
+ this.currentFramebuffer = tempFB;
2451
+ this.currentTexture = tempTex;
2452
+ }
2453
+ }
2454
+ /**
2455
+ * Update built-in uniforms from viji object
2456
+ */
2457
+ updateBuiltInUniforms(viji) {
2458
+ this.setUniform("u_resolution", "vec2", [viji.width, viji.height]);
2459
+ this.setUniform("u_time", "float", viji.time);
2460
+ this.setUniform("u_deltaTime", "float", viji.deltaTime);
2461
+ this.setUniform("u_frame", "int", viji.frameCount);
2462
+ this.setUniform("u_pixelRatio", "float", viji.pixelRatio);
2463
+ this.setUniform("u_fps", "float", viji.fps);
2464
+ this.setUniform("u_mouse", "vec2", [viji.mouse.x, viji.height - viji.mouse.y]);
2465
+ this.setUniform("u_mouseInCanvas", "bool", viji.mouse.isInCanvas);
2466
+ this.setUniform("u_mousePressed", "bool", viji.mouse.isPressed);
2467
+ this.setUniform("u_mouseLeft", "bool", viji.mouse.leftButton);
2468
+ this.setUniform("u_mouseRight", "bool", viji.mouse.rightButton);
2469
+ this.setUniform("u_mouseMiddle", "bool", viji.mouse.middleButton);
2470
+ this.setUniform("u_mouseVelocity", "vec2", [viji.mouse.velocity.x, -viji.mouse.velocity.y]);
2471
+ this.setUniform("u_keySpace", "bool", viji.keyboard.isPressed(" ") || viji.keyboard.isPressed("space"));
2472
+ this.setUniform("u_keyShift", "bool", viji.keyboard.shift);
2473
+ this.setUniform("u_keyCtrl", "bool", viji.keyboard.ctrl);
2474
+ this.setUniform("u_keyAlt", "bool", viji.keyboard.alt);
2475
+ this.setUniform("u_keyW", "bool", viji.keyboard.isPressed("w") || viji.keyboard.isPressed("W"));
2476
+ this.setUniform("u_keyA", "bool", viji.keyboard.isPressed("a") || viji.keyboard.isPressed("A"));
2477
+ this.setUniform("u_keyS", "bool", viji.keyboard.isPressed("s") || viji.keyboard.isPressed("S"));
2478
+ this.setUniform("u_keyD", "bool", viji.keyboard.isPressed("d") || viji.keyboard.isPressed("D"));
2479
+ this.setUniform("u_keyUp", "bool", viji.keyboard.isPressed("ArrowUp"));
2480
+ this.setUniform("u_keyDown", "bool", viji.keyboard.isPressed("ArrowDown"));
2481
+ this.setUniform("u_keyLeft", "bool", viji.keyboard.isPressed("ArrowLeft"));
2482
+ this.setUniform("u_keyRight", "bool", viji.keyboard.isPressed("ArrowRight"));
2483
+ this.setUniform("u_touchCount", "int", viji.touches.count);
2484
+ for (let i = 0; i < 5; i++) {
2485
+ const touch = viji.touches.points[i];
2486
+ if (touch) {
2487
+ this.setUniform(`u_touch${i}`, "vec2", [touch.x, viji.height - touch.y]);
2488
+ } else {
2489
+ this.setUniform(`u_touch${i}`, "vec2", [0, 0]);
2490
+ }
2491
+ }
2492
+ const audio = viji.audio;
2493
+ this.setUniform("u_audioVolume", "float", audio.volume?.rms || 0);
2494
+ this.setUniform("u_audioPeak", "float", audio.volume?.peak || 0);
2495
+ this.setUniform("u_audioBass", "float", audio.bands?.bass || 0);
2496
+ this.setUniform("u_audioMid", "float", audio.bands?.mid || 0);
2497
+ this.setUniform("u_audioTreble", "float", audio.bands?.treble || 0);
2498
+ this.setUniform("u_audioSubBass", "float", audio.bands?.subBass || 0);
2499
+ this.setUniform("u_audioLowMid", "float", audio.bands?.lowMid || 0);
2500
+ this.setUniform("u_audioHighMid", "float", audio.bands?.highMid || 0);
2501
+ this.setUniform("u_audioPresence", "float", audio.bands?.presence || 0);
2502
+ this.setUniform("u_audioBrilliance", "float", audio.bands?.brilliance || 0);
2503
+ if (audio.isConnected) {
2504
+ this.updateAudioFFTTexture(audio.getFrequencyData());
2505
+ }
2506
+ const video = viji.video;
2507
+ if (video.isConnected && video.currentFrame) {
2508
+ this.updateVideoTexture(video.currentFrame);
2509
+ this.setUniform("u_videoResolution", "vec2", [video.frameWidth, video.frameHeight]);
2510
+ this.setUniform("u_videoFrameRate", "float", video.frameRate);
2511
+ } else {
2512
+ this.setUniform("u_videoResolution", "vec2", [0, 0]);
2513
+ this.setUniform("u_videoFrameRate", "float", 0);
2514
+ }
2515
+ const faces = video.faces || [];
2516
+ this.setUniform("u_faceCount", "int", faces.length);
2517
+ if (faces.length > 0) {
2518
+ const face = faces[0];
2519
+ this.setUniform("u_face0Bounds", "vec4", [face.bounds.x, face.bounds.y, face.bounds.width, face.bounds.height]);
2520
+ this.setUniform("u_face0HeadPose", "vec3", [face.headPose.pitch, face.headPose.yaw, face.headPose.roll]);
2521
+ this.setUniform("u_face0Confidence", "float", face.confidence);
2522
+ this.setUniform("u_face0Happy", "float", face.expressions.happy);
2523
+ this.setUniform("u_face0Sad", "float", face.expressions.sad);
2524
+ this.setUniform("u_face0Angry", "float", face.expressions.angry);
2525
+ this.setUniform("u_face0Surprised", "float", face.expressions.surprised);
2526
+ } else {
2527
+ this.setUniform("u_face0Bounds", "vec4", [0, 0, 0, 0]);
2528
+ this.setUniform("u_face0HeadPose", "vec3", [0, 0, 0]);
2529
+ this.setUniform("u_face0Confidence", "float", 0);
2530
+ this.setUniform("u_face0Happy", "float", 0);
2531
+ this.setUniform("u_face0Sad", "float", 0);
2532
+ this.setUniform("u_face0Angry", "float", 0);
2533
+ this.setUniform("u_face0Surprised", "float", 0);
2534
+ }
2535
+ const hands = video.hands || [];
2536
+ this.setUniform("u_handCount", "int", hands.length);
2537
+ const leftHand = hands.find((h) => h.handedness === "left");
2538
+ const rightHand = hands.find((h) => h.handedness === "right");
2539
+ if (leftHand) {
2540
+ this.setUniform("u_leftHandPalm", "vec3", [leftHand.palm.x, leftHand.palm.y, leftHand.palm.z]);
2541
+ this.setUniform("u_leftHandFist", "float", leftHand.gestures?.fist || 0);
2542
+ this.setUniform("u_leftHandOpen", "float", leftHand.gestures?.openPalm || 0);
2543
+ } else {
2544
+ this.setUniform("u_leftHandPalm", "vec3", [0, 0, 0]);
2545
+ this.setUniform("u_leftHandFist", "float", 0);
2546
+ this.setUniform("u_leftHandOpen", "float", 0);
2547
+ }
2548
+ if (rightHand) {
2549
+ this.setUniform("u_rightHandPalm", "vec3", [rightHand.palm.x, rightHand.palm.y, rightHand.palm.z]);
2550
+ this.setUniform("u_rightHandFist", "float", rightHand.gestures?.fist || 0);
2551
+ this.setUniform("u_rightHandOpen", "float", rightHand.gestures?.openPalm || 0);
2552
+ } else {
2553
+ this.setUniform("u_rightHandPalm", "vec3", [0, 0, 0]);
2554
+ this.setUniform("u_rightHandFist", "float", 0);
2555
+ this.setUniform("u_rightHandOpen", "float", 0);
2556
+ }
2557
+ const pose = video.pose;
2558
+ this.setUniform("u_poseDetected", "bool", pose !== null);
2559
+ if (pose) {
2560
+ const nose = pose.landmarks[0];
2561
+ const leftWrist = pose.landmarks[15];
2562
+ const rightWrist = pose.landmarks[16];
2563
+ const leftAnkle = pose.landmarks[27];
2564
+ const rightAnkle = pose.landmarks[28];
2565
+ this.setUniform("u_nosePosition", "vec2", [nose?.x || 0, nose?.y || 0]);
2566
+ this.setUniform("u_leftWristPosition", "vec2", [leftWrist?.x || 0, leftWrist?.y || 0]);
2567
+ this.setUniform("u_rightWristPosition", "vec2", [rightWrist?.x || 0, rightWrist?.y || 0]);
2568
+ this.setUniform("u_leftAnklePosition", "vec2", [leftAnkle?.x || 0, leftAnkle?.y || 0]);
2569
+ this.setUniform("u_rightAnklePosition", "vec2", [rightAnkle?.x || 0, rightAnkle?.y || 0]);
2570
+ } else {
2571
+ this.setUniform("u_nosePosition", "vec2", [0, 0]);
2572
+ this.setUniform("u_leftWristPosition", "vec2", [0, 0]);
2573
+ this.setUniform("u_rightWristPosition", "vec2", [0, 0]);
2574
+ this.setUniform("u_leftAnklePosition", "vec2", [0, 0]);
2575
+ this.setUniform("u_rightAnklePosition", "vec2", [0, 0]);
2576
+ }
2577
+ const segmentation = video.segmentation;
2578
+ if (segmentation) {
2579
+ this.updateSegmentationTexture(segmentation.mask, segmentation.width, segmentation.height);
2580
+ this.setUniform("u_segmentationRes", "vec2", [segmentation.width, segmentation.height]);
2581
+ } else {
2582
+ this.setUniform("u_segmentationRes", "vec2", [0, 0]);
2583
+ }
2584
+ }
2585
+ /**
2586
+ * Update parameter uniforms from parameter objects
2587
+ */
2588
+ updateParameterUniforms(parameterObjects) {
2589
+ for (const param of this.parameters) {
2590
+ const paramObj = parameterObjects.get(param.uniformName);
2591
+ if (!paramObj) continue;
2592
+ const value = paramObj.value;
2593
+ switch (param.type) {
2594
+ case "slider":
2595
+ case "number":
2596
+ this.setUniform(param.uniformName, "float", value);
2597
+ break;
2598
+ case "color":
2599
+ const rgb = this.hexToRgb(value);
2600
+ this.setUniform(param.uniformName, "vec3", rgb);
2601
+ break;
2602
+ case "toggle":
2603
+ this.setUniform(param.uniformName, "bool", value);
2604
+ break;
2605
+ case "select":
2606
+ const index = param.config.options?.indexOf(value) || 0;
2607
+ this.setUniform(param.uniformName, "int", index);
2608
+ break;
2609
+ case "image":
2610
+ if (value) {
2611
+ this.updateImageTexture(param.uniformName, value);
2612
+ }
2613
+ break;
2614
+ }
2615
+ }
2616
+ }
2617
+ /**
2618
+ * Set uniform value
2619
+ */
2620
+ setUniform(name, type, value) {
2621
+ const location = this.uniformLocations.get(name);
2622
+ if (location === null || location === void 0) {
2623
+ return;
2624
+ }
2625
+ const gl = this.gl;
2626
+ switch (type) {
2627
+ case "float":
2628
+ gl.uniform1f(location, value);
2629
+ break;
2630
+ case "int":
2631
+ gl.uniform1i(location, value);
2632
+ break;
2633
+ case "bool":
2634
+ gl.uniform1i(location, value ? 1 : 0);
2635
+ break;
2636
+ case "vec2":
2637
+ gl.uniform2f(location, value[0], value[1]);
2638
+ break;
2639
+ case "vec3":
2640
+ gl.uniform3f(location, value[0], value[1], value[2]);
2641
+ break;
2642
+ case "vec4":
2643
+ gl.uniform4f(location, value[0], value[1], value[2], value[3]);
2644
+ break;
2645
+ }
2646
+ }
2647
+ /**
2648
+ * Convert hex color to RGB [0-1]
2649
+ */
2650
+ hexToRgb(hex) {
2651
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
2652
+ if (result) {
2653
+ return [
2654
+ parseInt(result[1], 16) / 255,
2655
+ parseInt(result[2], 16) / 255,
2656
+ parseInt(result[3], 16) / 255
2657
+ ];
2658
+ }
2659
+ return [0, 0, 0];
2660
+ }
2661
+ /**
2662
+ * Update audio FFT texture
2663
+ */
2664
+ updateAudioFFTTexture(frequencyData) {
2665
+ const gl = this.gl;
2666
+ const unit = this.textureUnits.get("u_audioFFT");
2667
+ if (!this.audioFFTTexture) {
2668
+ this.audioFFTTexture = gl.createTexture();
2669
+ }
2670
+ gl.activeTexture(gl.TEXTURE0 + unit);
2671
+ gl.bindTexture(gl.TEXTURE_2D, this.audioFFTTexture);
2672
+ gl.texImage2D(
2673
+ gl.TEXTURE_2D,
2674
+ 0,
2675
+ gl.LUMINANCE,
2676
+ frequencyData.length,
2677
+ 1,
2678
+ 0,
2679
+ gl.LUMINANCE,
2680
+ gl.UNSIGNED_BYTE,
2681
+ frequencyData
2682
+ );
2683
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
2684
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
2685
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
2686
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
2687
+ const location = this.uniformLocations.get("u_audioFFT");
2688
+ if (location) {
2689
+ gl.uniform1i(location, unit);
2690
+ }
2691
+ }
2692
+ /**
2693
+ * Update video texture
2694
+ */
2695
+ updateVideoTexture(videoFrame) {
2696
+ const gl = this.gl;
2697
+ const unit = this.textureUnits.get("u_video");
2698
+ if (!this.videoTexture) {
2699
+ this.videoTexture = gl.createTexture();
2700
+ }
2701
+ gl.activeTexture(gl.TEXTURE0 + unit);
2702
+ gl.bindTexture(gl.TEXTURE_2D, this.videoTexture);
2703
+ gl.texImage2D(
2704
+ gl.TEXTURE_2D,
2705
+ 0,
2706
+ gl.RGBA,
2707
+ gl.RGBA,
2708
+ gl.UNSIGNED_BYTE,
2709
+ videoFrame
2710
+ );
2711
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
2712
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
2713
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
2714
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
2715
+ const location = this.uniformLocations.get("u_video");
2716
+ if (location) {
2717
+ gl.uniform1i(location, unit);
2718
+ }
2719
+ }
2720
+ /**
2721
+ * Update segmentation mask texture
2722
+ */
2723
+ updateSegmentationTexture(mask, width, height) {
2724
+ const gl = this.gl;
2725
+ const unit = this.textureUnits.get("u_segmentationMask");
2726
+ if (!this.segmentationTexture) {
2727
+ this.segmentationTexture = gl.createTexture();
2728
+ }
2729
+ gl.activeTexture(gl.TEXTURE0 + unit);
2730
+ gl.bindTexture(gl.TEXTURE_2D, this.segmentationTexture);
2731
+ gl.texImage2D(
2732
+ gl.TEXTURE_2D,
2733
+ 0,
2734
+ gl.LUMINANCE,
2735
+ width,
2736
+ height,
2737
+ 0,
2738
+ gl.LUMINANCE,
2739
+ gl.UNSIGNED_BYTE,
2740
+ mask
2741
+ );
2742
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
2743
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
2744
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
2745
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
2746
+ const location = this.uniformLocations.get("u_segmentationMask");
2747
+ if (location) {
2748
+ gl.uniform1i(location, unit);
2749
+ }
2750
+ }
2751
+ /**
2752
+ * Update image parameter texture
2753
+ */
2754
+ updateImageTexture(name, imageBitmap) {
2755
+ const gl = this.gl;
2756
+ if (!this.textureUnits.has(name)) {
2757
+ this.textureUnits.set(name, this.nextTextureUnit++);
2758
+ }
2759
+ const unit = this.textureUnits.get(name);
2760
+ if (!this.textures.has(name)) {
2761
+ const texture2 = gl.createTexture();
2762
+ if (texture2) {
2763
+ this.textures.set(name, texture2);
2764
+ }
2765
+ }
2766
+ const texture = this.textures.get(name);
2767
+ if (!texture) return;
2768
+ gl.activeTexture(gl.TEXTURE0 + unit);
2769
+ gl.bindTexture(gl.TEXTURE_2D, texture);
2770
+ gl.texImage2D(
2771
+ gl.TEXTURE_2D,
2772
+ 0,
2773
+ gl.RGBA,
2774
+ gl.RGBA,
2775
+ gl.UNSIGNED_BYTE,
2776
+ imageBitmap
2777
+ );
2778
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
2779
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
2780
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
2781
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
2782
+ const location = this.uniformLocations.get(name);
2783
+ if (location) {
2784
+ gl.uniform1i(location, unit);
2785
+ }
2786
+ }
2787
+ /**
2788
+ * Handle canvas resize
2789
+ */
2790
+ resize(width, height) {
2791
+ const gl = this.gl;
2792
+ gl.viewport(0, 0, width, height);
2793
+ if (this.backbufferEnabled) {
2794
+ this.createBackbufferFramebuffers();
2795
+ }
2796
+ }
2797
+ /**
2798
+ * Get parameter definitions for host
2799
+ */
2800
+ getParameterDefinitions() {
2801
+ return this.parameters;
2802
+ }
2803
+ }
1530
2804
  class VijiWorkerRuntime {
1531
2805
  canvas = null;
1532
2806
  ctx = null;
@@ -1684,13 +2958,12 @@ class VijiWorkerRuntime {
1684
2958
  }
1685
2959
  /**
1686
2960
  * Initialize P5.js mode
1687
- * Lazy-loads P5WorkerAdapter and sets up P5 rendering
2961
+ * Sets up P5 rendering with P5WorkerAdapter
1688
2962
  */
1689
2963
  async initP5Mode(setup, render) {
1690
2964
  try {
1691
2965
  this.rendererType = "p5";
1692
2966
  this.debugLog("🎨 Initializing P5.js mode...");
1693
- const { P5WorkerAdapter } = await import("./renderers/P5WorkerAdapter-B4koBeZd.js");
1694
2967
  this.p5Adapter = new P5WorkerAdapter(
1695
2968
  this.canvas,
1696
2969
  this.viji,
@@ -1719,7 +2992,6 @@ class VijiWorkerRuntime {
1719
2992
  try {
1720
2993
  this.rendererType = "shader";
1721
2994
  this.debugLog("🎨 Initializing Shader mode...");
1722
- const { ShaderWorkerAdapter } = await import("./renderers/ShaderWorkerAdapter-Cn60jh9g.js");
1723
2995
  this.shaderAdapter = new ShaderWorkerAdapter(
1724
2996
  this.canvas,
1725
2997
  this.viji,
@@ -2232,4 +3504,4 @@ async function setSceneCode(sceneCode) {
2232
3504
  }
2233
3505
  }
2234
3506
  self.setSceneCode = setSceneCode;
2235
- //# sourceMappingURL=viji.worker-kGDstU5X.js.map
3507
+ //# sourceMappingURL=viji.worker-DHpeQQ14.js.map