@@ -9,70 +9,107 @@ alwaysApply: false
9
9
## Critical Rules
10
10
11
11
- New effects must follow one of two base patterns: `CanvasEffect<T>` (Canvas 2D) or `WebGLEffect<T>` (WebGL/Three.js)
12
- - Each effect requires at minimum three files:
13
- - `main.ts` - Entry point with effect registration
14
- - `template.html` - HTML template with control definitions using `<meta>` tags
15
- - Effect implementation file (e.g. , `effect-name-effect.ts`)
16
- - All controls must be defined in the template.html file with proper metadata
12
+ - Each effect requires at minimum two files:
13
+ - `main.ts` - Implementation with decorated effect class and control properties
14
+ - For WebGL effects: shader files like `fragment.glsl`
15
+ - All controls must be defined using decorator syntax: `@NumberControl` , `@ComboboxControl`, etc.
16
+ - Use the `@Effect` decorator to define effect metadata (name, description, author)
17
17
- WebGL effects must include shader code as separate `.glsl` files and import them
18
18
- Shadertoy conversions must adapt uniforms to match the LightScript naming convention
19
- - Control interfaces must extend `BaseControls` and define all parameters with proper types
19
+ - Control interfaces must define all parameters with proper types
20
20
- Implement all required abstract methods from the base class
21
21
- Canvas effects must implement the `draw(time, deltaTime)` method
22
22
- WebGL effects must implement `createUniforms()` and `updateUniforms()` methods
23
- - New effects must be registered in `src/index.ts` to be discoverable
23
+ - New effects must be registered in the effects registry to be discoverable
24
24
- Follow the pattern: initialize → loadResources → render → update → cleanup
25
25
- Always use strong typing with proper generics: `CanvasEffect<MyControlsInterface>`
26
- - Control values must be normalized using helper functions from `controls.ts`
26
+ - Control values must be normalized using helper functions
27
27
- Effect implementation must handle window resizing and device pixel ratio
28
28
- Use provided debug utilities with consistent log levels and namespaces
29
29
- Clean up all resources in the `stop()` method to prevent memory leaks
30
30
- Include comprehensive JSDoc comments for public methods
31
31
32
32
## Canvas Effect Creation Workflow
33
33
34
- 1. Create directory structure: `src/effects/{effect-id}/`
35
- 2. Define control interface extending `BaseControls`
36
- 3. Create template.html with control metadata
37
- 4. Implement effect class extending `CanvasEffect<T>`
38
- 5. Implement `draw()` method with Canvas 2D rendering logic
39
- 6. Register the effect in src/index.ts
34
+ 1. Create directory structure: `effects/{effect-id}/`
35
+ 2. Define control interface
36
+ 3. Implement effect class extending `CanvasEffect<T>` with appropriate decorators
37
+ 4. Implement `draw()` method with Canvas 2D rendering logic
38
+ 5. Register the effect in the effects registry
40
39
41
40
## WebGL Effect Creation Workflow
42
41
43
- 1. Create directory structure: `src/ effects/{effect-id}/`
42
+ 1. Create directory structure: `effects/{effect-id}/`
44
43
2. Define fragment shader (and optionally vertex shader) in .glsl files
45
- 3. Define control interface extending `BaseControls`
46
- 4. Create template.html with control metadata
47
- 5. Implement effect class extending `WebGLEffect<T>`
48
- 6. Implement shader uniform creation and updating
49
- 7. Register the effect in src/index.ts
44
+ 3. Define control interface
45
+ 4. Implement effect class extending `WebGLEffect<T>` with appropriate decorators
46
+ 5. Implement shader uniform creation and updating
47
+ 6. Register the effect in the effects registry
50
48
51
49
## Examples
52
50
53
51
<example>
54
52
// Canvas Effect Example (basic structure)
55
53
56
- // src/effects/my-canvas-effect/main.ts
57
- import { initializeEffect } from "../../common";
58
- import { MyCanvasEffect } from "./my-canvas-effect";
54
+ // effects/my-canvas-effect/main.ts
55
+ import { CanvasEffect } from "../../core/effects/canvas-effect";
56
+ import {
57
+ Effect,
58
+ NumberControl,
59
+ BooleanControl
60
+ } from "../../core/controls/decorators";
61
+ import { normalizeSpeed } from "../../core/controls/helpers";
62
+ import { initializeEffect } from "../../core";
59
63
60
- // Create and export the effect instance
61
- const effect = new MyCanvasEffect();
62
-
63
- // Initialize the effect
64
- initializeEffect(() => {
65
- effect.initialize();
66
- });
67
-
68
- export default effect;
69
-
70
- // src/effects/my-canvas-effect/my-canvas-effect.ts
71
- import { CanvasEffect } from "../../common/canvas-effect";
72
- import { MyEffectControls } from "./types";
73
- import { getControlValue, normalizeSpeed } from "../../common/controls";
64
+ // Define control interface
65
+ interface MyEffectControls {
66
+ speed: number;
67
+ particleCount: number;
68
+ useGlow: boolean;
69
+ colorIntensity: number;
70
+ }
74
71
72
+ @Effect({
73
+ name: "My Canvas Effect",
74
+ description: "A beautiful canvas-based effect",
75
+ author: "YourName"
76
+ })
75
77
export class MyCanvasEffect extends CanvasEffect<MyEffectControls> {
78
+ // Define controls with decorators
79
+ @NumberControl({
80
+ label: "Animation Speed",
81
+ min: 1,
82
+ max: 10,
83
+ default: 5,
84
+ tooltip: "Controls how fast the animation runs"
85
+ })
86
+ speed!: number;
87
+
88
+ @NumberControl({
89
+ label: "Particle Count",
90
+ min: 10,
91
+ max: 500,
92
+ default: 100,
93
+ tooltip: "Number of particles in the effect"
94
+ })
95
+ particleCount!: number;
96
+
97
+ @BooleanControl({
98
+ label: "Enable Glow",
99
+ default: true,
100
+ tooltip: "Enables glowing effect on particles"
101
+ })
102
+ useGlow!: boolean;
103
+
104
+ @NumberControl({
105
+ label: "Color Intensity",
106
+ min: 10,
107
+ max: 200,
108
+ default: 100,
109
+ tooltip: "Controls color brightness"
110
+ })
111
+ colorIntensity!: number;
112
+
76
113
// Effect properties
77
114
private particles = [];
78
115
private lastTime = 0;
@@ -86,19 +123,19 @@ export class MyCanvasEffect extends CanvasEffect<MyEffectControls> {
86
123
}
87
124
88
125
protected initializeControls(): void {
89
- // Set default values
126
+ // Set default values (now matches decorator defaults)
90
127
window.speed = 5;
91
128
window.particleCount = 100;
92
- window.useGlow = 1;
129
+ window.useGlow = true;
130
+ window.colorIntensity = 100;
93
131
}
94
132
95
133
protected getControlValues(): MyEffectControls {
96
134
return {
97
- speed: normalizeSpeed(getControlValue("speed", 5)),
98
- particleCount: getControlValue("particleCount", 100),
99
- useGlow: Boolean(getControlValue("useGlow", 1)),
100
- colorIntensity: getControlValue("colorIntensity", 100),
101
- colorSaturation: getControlValue("colorSaturation", 100)
135
+ speed: normalizeSpeed(window.speed ?? 5),
136
+ particleCount: window.particleCount ?? 100,
137
+ useGlow: Boolean(window.useGlow ?? true),
138
+ colorIntensity: window.colorIntensity ?? 100
102
139
};
103
140
}
104
141
@@ -119,19 +156,17 @@ export class MyCanvasEffect extends CanvasEffect<MyEffectControls> {
119
156
}
120
157
}
121
158
122
- // src/effects/my-canvas-effect/types.ts
123
- import { BaseControls } from "../../common/controls";
159
+ // Create and initialize the effect
160
+ const effect = new MyCanvasEffect();
161
+ initializeEffect(() => effect.initialize());
124
162
125
- export interface MyEffectControls extends BaseControls {
126
- particleCount: number;
127
- useGlow: boolean;
128
- }
163
+ export default effect;
129
164
</example>
130
165
131
166
<example>
132
167
// WebGL/Shadertoy Conversion Example
133
168
134
- // src/ effects/shadertoy-effect/fragment.glsl
169
+ // effects/shadertoy-effect/fragment.glsl
135
170
// Modified Shadertoy shader with LightScript uniforms
136
171
uniform float iTime;
137
172
uniform vec2 iResolution;
@@ -166,13 +201,19 @@ void main() {
166
201
mainImage(gl_FragColor, gl_FragCoord.xy);
167
202
}
168
203
169
- // src/effects/shadertoy-effect/main.ts
170
- import { WebGLEffect } from "../../common/webgl-effect";
171
- import { initializeEffect } from "../../common";
204
+ // effects/shadertoy-effect/main.ts
205
+ import { WebGLEffect } from "../../core/effects/webgl-effect";
206
+ import {
207
+ Effect,
208
+ NumberControl,
209
+ ComboboxControl
210
+ } from "../../core/controls/decorators";
211
+ import { normalizeSpeed } from "../../core/controls/helpers";
212
+ import { initializeEffect } from "../../core";
172
213
import * as THREE from "three";
173
214
import fragmentShader from "./fragment.glsl";
174
- import { getControlValue, normalizeSpeed } from "../../common/controls";
175
215
216
+ // Define control interface
176
217
interface ShadertoyEffectControls {
177
218
speed: number;
178
219
colorIntensity: number;
@@ -181,9 +222,58 @@ interface ShadertoyEffectControls {
181
222
colorSaturation: number;
182
223
}
183
224
184
- class ShadertoyEffect extends WebGLEffect<ShadertoyEffectControls> {
225
+ @Effect({
226
+ name: "Shadertoy Effect",
227
+ description: "Converted from Shadertoy",
228
+ author: "YourName"
229
+ })
230
+ export class ShadertoyEffect extends WebGLEffect<ShadertoyEffectControls> {
185
231
private readonly colorModes = ["Standard", "Neon", "Monochrome"];
186
232
233
+ @NumberControl({
234
+ label: "Animation Speed",
235
+ min: 1,
236
+ max: 10,
237
+ default: 5,
238
+ tooltip: "Controls animation speed"
239
+ })
240
+ speed!: number;
241
+
242
+ @NumberControl({
243
+ label: "Color Intensity",
244
+ min: 10,
245
+ max: 200,
246
+ default: 100,
247
+ tooltip: "Brightness of colors"
248
+ })
249
+ colorIntensity!: number;
250
+
251
+ @ComboboxControl({
252
+ label: "Color Mode",
253
+ values: ["Standard", "Neon", "Monochrome"],
254
+ default: "Standard",
255
+ tooltip: "Changes the color scheme"
256
+ })
257
+ colorMode!: string;
258
+
259
+ @NumberControl({
260
+ label: "Pattern Density",
261
+ min: 1,
262
+ max: 10,
263
+ default: 5,
264
+ tooltip: "Controls pattern density"
265
+ })
266
+ pattern!: number;
267
+
268
+ @NumberControl({
269
+ label: "Color Saturation",
270
+ min: 0,
271
+ max: 200,
272
+ default: 100,
273
+ tooltip: "Controls color saturation"
274
+ })
275
+ colorSaturation!: number;
276
+
187
277
constructor() {
188
278
super({
189
279
id: "shadertoy-effect",
@@ -202,7 +292,7 @@ class ShadertoyEffect extends WebGLEffect<ShadertoyEffectControls> {
202
292
203
293
protected getControlValues(): ShadertoyEffectControls {
204
294
// Handle colorMode conversion
205
- const rawColorMode = getControlValue(" colorMode", "Standard") ;
295
+ const rawColorMode = window. colorMode ?? "Standard";
206
296
let colorMode: number | string = rawColorMode;
207
297
208
298
if (typeof colorMode === "string") {
@@ -211,11 +301,11 @@ class ShadertoyEffect extends WebGLEffect<ShadertoyEffectControls> {
211
301
}
212
302
213
303
return {
214
- speed: normalizeSpeed(getControlValue(" speed", 5) ),
215
- colorIntensity: getControlValue(" colorIntensity", 100) / 100,
304
+ speed: normalizeSpeed(window. speed ?? 5 ),
305
+ colorIntensity: (window. colorIntensity ?? 100) / 100,
216
306
colorMode,
217
- pattern: getControlValue(" pattern", 5) ,
218
- colorSaturation: getControlValue(" colorSaturation", 100)
307
+ pattern: window. pattern ?? 5 ,
308
+ colorSaturation: window. colorSaturation ?? 100
219
309
};
220
310
}
221
311
@@ -245,44 +335,43 @@ export default effect;
245
335
</example>
246
336
247
337
<example type="invalid">
248
- // Invalid WebGL effect implementation
249
-
250
- // Missing separate shader file, incorrect directory structure
251
- // main.js in src/ root instead of proper structure
338
+ // Invalid effect implementation
252
339
253
- // Direct DOM manipulation instead of using framework
254
- const canvas = document.getElementById("canvas");
255
- const gl = canvas.getContext("webgl");
256
-
257
- // Inline shaders instead of separate files
258
- const vertexShader = `
259
- attribute vec4 aPosition;
260
- void main() {
261
- gl_Position = aPosition;
262
- }
263
- `;
264
-
265
- // Not using base classes
266
- class MyEffect {
340
+ // Missing decorators for controls
341
+ class MyEffect extends WebGLEffect<MyControls> {
342
+ // Controls defined without decorators
343
+ private speed = 5;
344
+ private colorMode = "Standard";
345
+
346
+ // Missing @Effect decorator
267
347
constructor() {
268
- this.speed = 1.0;
269
- this.init();
348
+ super({
349
+ id: "my-effect",
350
+ name: "My Effect",
351
+ fragmentShader
352
+ });
270
353
}
271
354
272
- init() {
355
+ // Trying to directly manipulate DOM instead of using framework
356
+ protected initializeRenderer(): Promise<void> {
357
+ const canvas = document.getElementById("canvas");
358
+ const gl = canvas.getContext("webgl");
273
359
// Direct WebGL calls instead of using Three.js
274
- const program = gl.createProgram();
275
- // ...more WebGL code
276
360
}
277
361
278
- update() {
279
- // Not using the lifecycle methods
362
+ // Missing required methods
363
+ // No createUniforms() or updateUniforms() implementation
364
+
365
+ // Missing proper control updates
366
+ protected updateParameters(controls: MyControls): void {
367
+ // Directly setting uniform values without checking if material exists
368
+ this.material.uniforms.iSpeed.value = controls.speed;
280
369
}
281
370
282
371
// Missing proper cleanup
283
372
}
284
373
285
- // Not registering in index.ts
286
- const myEffect = new MyEffect();
374
+ // Not providing proper initialization
375
+ new MyEffect().initialize ();
287
376
</example>
288
377
</rewritten_file>
0 commit comments