@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.
- package/dist/artist-dts.js +1 -1
- package/dist/artist-global.d.ts +1 -1
- package/dist/assets/{viji.worker-kGDstU5X.js → viji.worker-DHpeQQ14.js} +1276 -4
- package/dist/assets/viji.worker-DHpeQQ14.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/renderers/P5WorkerAdapter-B4koBeZd.js +0 -343
- package/dist/assets/renderers/P5WorkerAdapter-B4koBeZd.js.map +0 -1
- package/dist/assets/renderers/ShaderWorkerAdapter-Cn60jh9g.js +0 -939
- package/dist/assets/renderers/ShaderWorkerAdapter-Cn60jh9g.js.map +0 -1
- package/dist/assets/viji.worker-kGDstU5X.js.map +0 -1
|
@@ -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
|
-
*
|
|
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-
|
|
3507
|
+
//# sourceMappingURL=viji.worker-DHpeQQ14.js.map
|