@viji-dev/core 0.3.39 → 0.4.1

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.
@@ -1,3 +1,300 @@
1
+ function parseColorInput(input2) {
2
+ if (typeof input2 === "string") {
3
+ return parseColorString(input2);
4
+ }
5
+ if (input2 && typeof input2 === "object") {
6
+ const obj = input2;
7
+ const hasRgbKeys = "r" in obj && "g" in obj && "b" in obj;
8
+ const hasHsbKeys = "h" in obj && "s" in obj && "b" in obj;
9
+ if (hasHsbKeys && !("r" in obj) && !("g" in obj)) {
10
+ return hsbObjectToHex(obj);
11
+ }
12
+ if (hasRgbKeys) {
13
+ return rgbObjectToHex(obj);
14
+ }
15
+ throw new Error(
16
+ `Invalid color object: expected {r,g,b} or {h,s,b} keys, got ${JSON.stringify(input2)}`
17
+ );
18
+ }
19
+ throw new Error(`Invalid color input: expected string or object, got ${typeof input2}`);
20
+ }
21
+ function hexToRgb255(hex) {
22
+ const m = /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(hex);
23
+ if (!m) {
24
+ throw new Error(`Expected canonical hex '#rrggbb', got: ${hex}`);
25
+ }
26
+ return {
27
+ r: parseInt(m[1], 16),
28
+ g: parseInt(m[2], 16),
29
+ b: parseInt(m[3], 16)
30
+ };
31
+ }
32
+ function hexToHsb(hex) {
33
+ const { r, g, b } = hexToRgb255(hex);
34
+ const rN = r / 255;
35
+ const gN = g / 255;
36
+ const bN = b / 255;
37
+ const max = Math.max(rN, gN, bN);
38
+ const min = Math.min(rN, gN, bN);
39
+ const delta = max - min;
40
+ let h = 0;
41
+ if (delta > 0) {
42
+ if (max === rN) {
43
+ h = (gN - bN) / delta % 6;
44
+ } else if (max === gN) {
45
+ h = (bN - rN) / delta + 2;
46
+ } else {
47
+ h = (rN - gN) / delta + 4;
48
+ }
49
+ h *= 60;
50
+ if (h < 0) h += 360;
51
+ }
52
+ const s = max === 0 ? 0 : delta / max * 100;
53
+ const brightness = max * 100;
54
+ return {
55
+ h: roundTo(h, 4),
56
+ s: roundTo(s, 4),
57
+ b: roundTo(brightness, 4)
58
+ };
59
+ }
60
+ function hexToRgb01(hex) {
61
+ const { r, g, b } = hexToRgb255(hex);
62
+ return [r / 255, g / 255, b / 255];
63
+ }
64
+ function rgbToHex(r, g, b) {
65
+ const rByte = clampByte(r, "r");
66
+ const gByte = clampByte(g, "g");
67
+ const bByte = clampByte(b, "b");
68
+ return "#" + toHexByte(rByte) + toHexByte(gByte) + toHexByte(bByte);
69
+ }
70
+ function hsbToHex(h, s, b) {
71
+ if (typeof h !== "number" || typeof s !== "number" || typeof b !== "number" || !isFinite(h) || !isFinite(s) || !isFinite(b)) {
72
+ throw new Error(`Invalid HSB: h=${h}, s=${s}, b=${b}`);
73
+ }
74
+ if (s < 0 || s > 100) {
75
+ throw new Error(`HSB saturation must be 0..100, got: ${s}`);
76
+ }
77
+ if (b < 0 || b > 100) {
78
+ throw new Error(`HSB brightness must be 0..100, got: ${b}`);
79
+ }
80
+ const hue = (h % 360 + 360) % 360;
81
+ const sN = s / 100;
82
+ const bN = b / 100;
83
+ const c = bN * sN;
84
+ const hPrime = hue / 60;
85
+ const x = c * (1 - Math.abs(hPrime % 2 - 1));
86
+ let r1 = 0, g1 = 0, b1 = 0;
87
+ if (hPrime < 1) {
88
+ r1 = c;
89
+ g1 = x;
90
+ b1 = 0;
91
+ } else if (hPrime < 2) {
92
+ r1 = x;
93
+ g1 = c;
94
+ b1 = 0;
95
+ } else if (hPrime < 3) {
96
+ r1 = 0;
97
+ g1 = c;
98
+ b1 = x;
99
+ } else if (hPrime < 4) {
100
+ r1 = 0;
101
+ g1 = x;
102
+ b1 = c;
103
+ } else if (hPrime < 5) {
104
+ r1 = x;
105
+ g1 = 0;
106
+ b1 = c;
107
+ } else {
108
+ r1 = c;
109
+ g1 = 0;
110
+ b1 = x;
111
+ }
112
+ const m = bN - c;
113
+ return rgbToHex(
114
+ Math.round((r1 + m) * 255),
115
+ Math.round((g1 + m) * 255),
116
+ Math.round((b1 + m) * 255)
117
+ );
118
+ }
119
+ function parseColorString(input2) {
120
+ const trimmed = input2.trim();
121
+ if (trimmed.startsWith("#")) {
122
+ return parseHexString(trimmed);
123
+ }
124
+ const fnMatch = trimmed.match(/^(rgb|hsl|hsb|vec3)\s*\(([^)]*)\)$/i);
125
+ if (fnMatch) {
126
+ const fnName = fnMatch[1].toLowerCase();
127
+ const args = fnMatch[2];
128
+ switch (fnName) {
129
+ case "rgb":
130
+ return parseRgbFunction(args);
131
+ case "hsl":
132
+ return parseHslFunction(args);
133
+ case "hsb":
134
+ return parseHsbFunction(args);
135
+ case "vec3":
136
+ return parseVec3Function(args);
137
+ }
138
+ }
139
+ throw new Error(
140
+ `Invalid color string: '${input2}'. Expected #rrggbb, #rgb, rgb(...), hsl(...), hsb(...), or vec3(...)`
141
+ );
142
+ }
143
+ function parseHexString(hex) {
144
+ if (/^#[0-9a-fA-F]{6}$/.test(hex)) {
145
+ return hex.toLowerCase();
146
+ }
147
+ if (/^#[0-9a-fA-F]{3}$/.test(hex)) {
148
+ const r = hex[1];
149
+ const g = hex[2];
150
+ const b = hex[3];
151
+ return ("#" + r + r + g + g + b + b).toLowerCase();
152
+ }
153
+ throw new Error(`Invalid hex color: '${hex}'. Expected #rrggbb or #rgb`);
154
+ }
155
+ function parseRgbFunction(args) {
156
+ const parts = splitArgs(args);
157
+ if (parts.length !== 3) {
158
+ throw new Error(`rgb() requires 3 arguments, got ${parts.length}: rgb(${args})`);
159
+ }
160
+ const r = parseNumber(parts[0], "r");
161
+ const g = parseNumber(parts[1], "g");
162
+ const b = parseNumber(parts[2], "b");
163
+ return rgbToHex(r, g, b);
164
+ }
165
+ function parseHslFunction(args) {
166
+ const parts = splitArgs(args);
167
+ if (parts.length !== 3) {
168
+ throw new Error(`hsl() requires 3 arguments, got ${parts.length}: hsl(${args})`);
169
+ }
170
+ const h = parseNumber(parts[0], "h");
171
+ const s = parsePercent(parts[1], "s");
172
+ const l = parsePercent(parts[2], "l");
173
+ return hslToHex(h, s, l);
174
+ }
175
+ function parseHsbFunction(args) {
176
+ const parts = splitArgs(args);
177
+ if (parts.length !== 3) {
178
+ throw new Error(`hsb() requires 3 arguments, got ${parts.length}: hsb(${args})`);
179
+ }
180
+ const h = parseNumber(parts[0], "h");
181
+ const s = parseNumber(parts[1], "s");
182
+ const b = parseNumber(parts[2], "b");
183
+ return hsbToHex(h, s, b);
184
+ }
185
+ function parseVec3Function(args) {
186
+ const parts = splitArgs(args);
187
+ if (parts.length !== 3) {
188
+ throw new Error(`vec3() requires 3 arguments, got ${parts.length}: vec3(${args})`);
189
+ }
190
+ const r = parseNumber(parts[0], "r");
191
+ const g = parseNumber(parts[1], "g");
192
+ const b = parseNumber(parts[2], "b");
193
+ if (r < 0 || r > 1 || g < 0 || g > 1 || b < 0 || b > 1) {
194
+ throw new Error(`vec3() components must be in 0..1, got: vec3(${r}, ${g}, ${b})`);
195
+ }
196
+ return rgbToHex(
197
+ Math.round(r * 255),
198
+ Math.round(g * 255),
199
+ Math.round(b * 255)
200
+ );
201
+ }
202
+ function rgbObjectToHex(obj) {
203
+ const r = numericField(obj.r, "r");
204
+ const g = numericField(obj.g, "g");
205
+ const b = numericField(obj.b, "b");
206
+ return rgbToHex(r, g, b);
207
+ }
208
+ function hsbObjectToHex(obj) {
209
+ const h = numericField(obj.h, "h");
210
+ const s = numericField(obj.s, "s");
211
+ const b = numericField(obj.b, "b");
212
+ return hsbToHex(h, s, b);
213
+ }
214
+ function hslToHex(h, s, l) {
215
+ if (s < 0 || s > 100) {
216
+ throw new Error(`HSL saturation must be 0..100%, got: ${s}%`);
217
+ }
218
+ if (l < 0 || l > 100) {
219
+ throw new Error(`HSL lightness must be 0..100%, got: ${l}%`);
220
+ }
221
+ const hue = (h % 360 + 360) % 360;
222
+ const sN = s / 100;
223
+ const lN = l / 100;
224
+ const c = (1 - Math.abs(2 * lN - 1)) * sN;
225
+ const hPrime = hue / 60;
226
+ const x = c * (1 - Math.abs(hPrime % 2 - 1));
227
+ let r1 = 0, g1 = 0, b1 = 0;
228
+ if (hPrime < 1) {
229
+ r1 = c;
230
+ g1 = x;
231
+ b1 = 0;
232
+ } else if (hPrime < 2) {
233
+ r1 = x;
234
+ g1 = c;
235
+ b1 = 0;
236
+ } else if (hPrime < 3) {
237
+ r1 = 0;
238
+ g1 = c;
239
+ b1 = x;
240
+ } else if (hPrime < 4) {
241
+ r1 = 0;
242
+ g1 = x;
243
+ b1 = c;
244
+ } else if (hPrime < 5) {
245
+ r1 = x;
246
+ g1 = 0;
247
+ b1 = c;
248
+ } else {
249
+ r1 = c;
250
+ g1 = 0;
251
+ b1 = x;
252
+ }
253
+ const m = lN - c / 2;
254
+ return rgbToHex(
255
+ Math.round((r1 + m) * 255),
256
+ Math.round((g1 + m) * 255),
257
+ Math.round((b1 + m) * 255)
258
+ );
259
+ }
260
+ function splitArgs(args) {
261
+ return args.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
262
+ }
263
+ function parseNumber(token, fieldName) {
264
+ const n = parseFloat(token);
265
+ if (!isFinite(n)) {
266
+ throw new Error(`Invalid number for '${fieldName}': '${token}'`);
267
+ }
268
+ return n;
269
+ }
270
+ function parsePercent(token, fieldName) {
271
+ if (!token.endsWith("%")) {
272
+ throw new Error(`Field '${fieldName}' must use percent notation (e.g., 50%), got: '${token}'`);
273
+ }
274
+ return parseNumber(token.slice(0, -1), fieldName);
275
+ }
276
+ function numericField(value, fieldName) {
277
+ if (typeof value !== "number" || !isFinite(value)) {
278
+ throw new Error(`Invalid color field '${fieldName}': expected finite number, got ${value}`);
279
+ }
280
+ return value;
281
+ }
282
+ function clampByte(value, fieldName) {
283
+ if (typeof value !== "number" || !isFinite(value)) {
284
+ throw new Error(`RGB component '${fieldName}' must be a finite number, got: ${value}`);
285
+ }
286
+ if (value < 0 || value > 255) {
287
+ throw new Error(`RGB component '${fieldName}' must be 0..255, got: ${value}`);
288
+ }
289
+ return Math.round(value);
290
+ }
291
+ function toHexByte(byte) {
292
+ return byte.toString(16).padStart(2, "0");
293
+ }
294
+ function roundTo(value, digits) {
295
+ const factor = 10 ** digits;
296
+ return Math.round(value * factor) / factor;
297
+ }
1
298
  class ParameterSystem {
2
299
  // Parameter system for Phase 2 (new object-based approach)
3
300
  parameterDefinitions = /* @__PURE__ */ new Map();
@@ -61,8 +358,11 @@ class ParameterSystem {
61
358
  }
62
359
  createColorParameter(defaultValue, config) {
63
360
  const paramName = config.label;
361
+ const hex = parseColorInput(defaultValue);
64
362
  const colorObject = {
65
- value: defaultValue,
363
+ value: hex,
364
+ rgb: Object.freeze(hexToRgb255(hex)),
365
+ hsb: Object.freeze(hexToHsb(hex)),
66
366
  label: config.label,
67
367
  description: config.description ?? "",
68
368
  group: config.group ?? "general",
@@ -70,7 +370,7 @@ class ParameterSystem {
70
370
  };
71
371
  const definition = {
72
372
  type: "color",
73
- defaultValue,
373
+ defaultValue: hex,
74
374
  label: colorObject.label,
75
375
  description: colorObject.description,
76
376
  group: colorObject.group,
@@ -259,6 +559,14 @@ class ParameterSystem {
259
559
  console.warn(`Unknown parameter: ${name}. Available parameters:`, Array.from(this.parameterDefinitions.keys()));
260
560
  return false;
261
561
  }
562
+ if (definition.type === "color") {
563
+ try {
564
+ value = parseColorInput(value);
565
+ } catch (err) {
566
+ console.error(`Parameter '${name}' invalid color: ${err.message}`);
567
+ return false;
568
+ }
569
+ }
262
570
  if (!this.validateParameterValue(name, value, definition)) {
263
571
  console.warn(`Validation failed for parameter ${name} = ${value}`);
264
572
  return false;
@@ -273,6 +581,11 @@ class ParameterSystem {
273
581
  const parameterObject = this.parameterObjects.get(name);
274
582
  if (parameterObject) {
275
583
  parameterObject.value = value;
584
+ if (definition.type === "color") {
585
+ const hex = value;
586
+ parameterObject.rgb = Object.freeze(hexToRgb255(hex));
587
+ parameterObject.hsb = Object.freeze(hexToHsb(hex));
588
+ }
276
589
  }
277
590
  return true;
278
591
  }
@@ -298,8 +611,8 @@ class ParameterSystem {
298
611
  }
299
612
  break;
300
613
  case "color":
301
- if (typeof value !== "string" || !/^#[0-9A-Fa-f]{6}$/.test(value)) {
302
- console.error(`Parameter '${name}' must be a valid hex color, got: ${value}`);
614
+ if (typeof value !== "string" || !/^#[0-9a-f]{6}$/.test(value)) {
615
+ console.error(`Parameter '${name}' must be normalized lowercase hex '#rrggbb', got: ${value}`);
303
616
  return false;
304
617
  }
305
618
  break;
@@ -337,7 +650,7 @@ class ParameterSystem {
337
650
  return false;
338
651
  }
339
652
  break;
340
- case "coordinate":
653
+ case "coordinate": {
341
654
  if (!value || typeof value !== "object" || !("x" in value) || !("y" in value)) {
342
655
  console.error(`Parameter '${name}' must be an object with x and y properties, got: ${value}`);
343
656
  return false;
@@ -352,6 +665,7 @@ class ParameterSystem {
352
665
  return false;
353
666
  }
354
667
  break;
668
+ }
355
669
  }
356
670
  return true;
357
671
  }
@@ -2298,7 +2612,7 @@ class ShaderParameterParser {
2298
2612
  */
2299
2613
  static parseKeyValuePairs(configStr) {
2300
2614
  const config = {};
2301
- const keyValueRegex = /(\w+):((?:"[^"]*"|\[[^\]]*\]|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|[^\s]+))/g;
2615
+ const keyValueRegex = /(\w+):((?:"[^"]*"|\[[^\]]*\]|#[0-9a-fA-F]{6}|#[0-9a-fA-F]{3}|(?:rgb|hsl|hsb|vec3)\s*\([^)]*\)|[^\s]+))/g;
2302
2616
  let match2;
2303
2617
  while ((match2 = keyValueRegex.exec(configStr)) !== null) {
2304
2618
  const [, key, value] = match2;
@@ -2330,6 +2644,9 @@ class ShaderParameterParser {
2330
2644
  if (value.startsWith("#")) {
2331
2645
  return value;
2332
2646
  }
2647
+ if (/^(?:rgb|hsl|hsb|vec3)\s*\(/i.test(value)) {
2648
+ return value;
2649
+ }
2333
2650
  if (value === "true") return true;
2334
2651
  if (value === "false") return false;
2335
2652
  const num = parseFloat(value);
@@ -2360,8 +2677,12 @@ class ShaderParameterParser {
2360
2677
  if (param.config.default === void 0) {
2361
2678
  throw new Error(`Parameter ${param.uniformName} of type 'color' missing required 'default' key`);
2362
2679
  }
2363
- if (!param.config.default.startsWith("#")) {
2364
- throw new Error(`Parameter ${param.uniformName} of type 'color' default must be hex color (e.g., #ff0000)`);
2680
+ try {
2681
+ param.config.default = parseColorInput(param.config.default);
2682
+ } catch (err) {
2683
+ throw new Error(
2684
+ `Parameter ${param.uniformName} of type 'color' has invalid default: ${err.message}`
2685
+ );
2365
2686
  }
2366
2687
  break;
2367
2688
  case "toggle":
@@ -2516,6 +2837,10 @@ class ShaderWorkerAdapter {
2516
2837
  accumulatorWarned = /* @__PURE__ */ new Set();
2517
2838
  // Cache of scalar uniform values for accumulator built-in rate lookups
2518
2839
  scalarUniformCache = /* @__PURE__ */ new Map();
2840
+ // Cache of hex -> normalized [r, g, b] (0..1) for color uniforms.
2841
+ // Avoids re-parsing the hex string on every frame; invalidated when the hex value changes.
2842
+ colorVec3Cache = /* @__PURE__ */ new Map();
2843
+ colorVec3CacheKey = /* @__PURE__ */ new Map();
2519
2844
  // Backbuffer support (ping-pong framebuffers)
2520
2845
  backbufferFramebuffer = null;
2521
2846
  backbufferTexture = null;
@@ -3746,17 +4071,23 @@ ${error}`);
3746
4071
  case "number":
3747
4072
  this.setUniform(param.uniformName, "float", value);
3748
4073
  break;
3749
- case "color":
3750
- const rgb = this.hexToRgb(value);
3751
- this.setUniform(param.uniformName, "vec3", rgb);
4074
+ case "color": {
4075
+ const hex = value;
4076
+ if (this.colorVec3CacheKey.get(param.uniformName) !== hex) {
4077
+ this.colorVec3Cache.set(param.uniformName, hexToRgb01(hex));
4078
+ this.colorVec3CacheKey.set(param.uniformName, hex);
4079
+ }
4080
+ this.setUniform(param.uniformName, "vec3", this.colorVec3Cache.get(param.uniformName));
3752
4081
  break;
4082
+ }
3753
4083
  case "toggle":
3754
4084
  this.setUniform(param.uniformName, "bool", value);
3755
4085
  break;
3756
- case "select":
4086
+ case "select": {
3757
4087
  const index = param.config.options?.indexOf(value) || 0;
3758
4088
  this.setUniform(param.uniformName, "int", index);
3759
4089
  break;
4090
+ }
3760
4091
  case "image":
3761
4092
  if (value) {
3762
4093
  this.updateImageTexture(param.uniformName, value);
@@ -3827,20 +4158,6 @@ ${error}`);
3827
4158
  break;
3828
4159
  }
3829
4160
  }
3830
- /**
3831
- * Convert hex color to RGB [0-1]
3832
- */
3833
- hexToRgb(hex) {
3834
- const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
3835
- if (result) {
3836
- return [
3837
- parseInt(result[1], 16) / 255,
3838
- parseInt(result[2], 16) / 255,
3839
- parseInt(result[3], 16) / 255
3840
- ];
3841
- }
3842
- return [0, 0, 0];
3843
- }
3844
4161
  /**
3845
4162
  * Update audio FFT texture
3846
4163
  */
@@ -27128,4 +27445,4 @@ async function setSceneCode(sceneCode) {
27128
27445
  }
27129
27446
  }
27130
27447
  self.setSceneCode = setSceneCode;
27131
- //# sourceMappingURL=viji.worker-BoI8e3NI.js.map
27448
+ //# sourceMappingURL=viji.worker-4gGFik2A.js.map