first commit

This commit is contained in:
2025-07-05 02:34:28 +02:00
commit 423b02a195
11 changed files with 2240 additions and 0 deletions

237
src/FakeShader.ts Normal file
View File

@ -0,0 +1,237 @@
interface WorkerMessage {
id: string;
type: 'compile' | 'render';
code?: string;
width?: number;
height?: number;
time?: number;
}
interface WorkerResponse {
id: string;
type: 'compiled' | 'rendered' | 'error';
success: boolean;
imageData?: ImageData;
error?: string;
}
export class FakeShader {
private canvas: HTMLCanvasElement;
private ctx: CanvasRenderingContext2D;
private code: string;
private worker: Worker;
private animationId: number | null = null;
private startTime: number = Date.now();
private isCompiled: boolean = false;
private isRendering: boolean = false;
private pendingRenders: string[] = [];
// Frame rate limiting
private targetFPS: number = 30;
private frameInterval: number = 1000 / this.targetFPS;
private lastFrameTime: number = 0;
constructor(canvas: HTMLCanvasElement, code: string = 'x^y') {
this.canvas = canvas;
this.ctx = canvas.getContext('2d')!;
this.code = code;
// Initialize worker
this.worker = new Worker(new URL('./ShaderWorker.ts', import.meta.url), { type: 'module' });
this.worker.onmessage = (e: MessageEvent<WorkerResponse>) => this.handleWorkerMessage(e.data);
this.worker.onerror = (error) => console.error('Worker error:', error);
this.compile();
}
private handleWorkerMessage(response: WorkerResponse): void {
switch (response.type) {
case 'compiled':
this.isCompiled = response.success;
if (!response.success) {
console.error('Compilation failed:', response.error);
this.fillBlack();
}
break;
case 'rendered':
this.isRendering = false;
if (response.success && response.imageData) {
this.ctx.putImageData(response.imageData, 0, 0);
} else {
console.error('Render failed:', response.error);
this.fillBlack();
}
// Process pending renders
if (this.pendingRenders.length > 0) {
this.pendingRenders.shift(); // Remove completed render
if (this.pendingRenders.length > 0) {
// Skip to latest render request
const latestId = this.pendingRenders[this.pendingRenders.length - 1];
this.pendingRenders = [latestId];
this.executeRender(latestId);
}
}
break;
case 'error':
this.isRendering = false;
console.error('Worker error:', response.error);
this.fillBlack();
break;
}
}
private fillBlack(): void {
this.ctx.fillStyle = '#000';
this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
}
private compile(): void {
this.isCompiled = false;
const id = `compile_${Date.now()}`;
this.worker.postMessage({
id,
type: 'compile',
code: this.code
} as WorkerMessage);
}
private executeRender(id: string): void {
if (!this.isCompiled || this.isRendering) {
return;
}
this.isRendering = true;
const currentTime = (Date.now() - this.startTime) / 1000;
this.worker.postMessage({
id,
type: 'render',
width: this.canvas.width,
height: this.canvas.height,
time: currentTime
} as WorkerMessage);
}
setCode(code: string): void {
this.code = code;
this.compile();
}
render(animate: boolean = false): void {
const currentTime = performance.now();
// Frame rate limiting
if (animate && currentTime - this.lastFrameTime < this.frameInterval) {
if (animate) {
this.animationId = requestAnimationFrame(() => this.render(true));
}
return;
}
this.lastFrameTime = currentTime;
if (!this.isCompiled) {
this.fillBlack();
if (animate) {
this.animationId = requestAnimationFrame(() => this.render(true));
}
return;
}
const renderId = `render_${Date.now()}_${Math.random()}`;
// Add to pending renders queue
this.pendingRenders.push(renderId);
// If not currently rendering, start immediately
if (!this.isRendering) {
this.executeRender(renderId);
}
// Continue animation
if (animate) {
this.animationId = requestAnimationFrame(() => this.render(true));
}
}
startAnimation(): void {
this.stopAnimation();
this.startTime = Date.now();
this.lastFrameTime = 0; // Reset frame timing
this.render(true);
}
stopAnimation(): void {
if (this.animationId !== null) {
cancelAnimationFrame(this.animationId);
this.animationId = null;
}
// Clear pending renders
this.pendingRenders = [];
}
setTargetFPS(fps: number): void {
this.targetFPS = Math.max(1, Math.min(120, fps)); // Clamp between 1-120 FPS
this.frameInterval = 1000 / this.targetFPS;
}
destroy(): void {
this.stopAnimation();
this.worker.terminate();
}
static generateRandomCode(): string {
const presets = [
'x^y',
'x&y',
'x|y',
'(x*y)%256',
'(x+y+t*10)%256',
'((x>>4)^(y>>4))<<4',
'(x^y^(x*y))%256',
'((x&y)|(x^y))%256',
'(x+y)&255',
'x%y',
'(x^(y<<2))%256',
'((x*t)^y)%256',
'(x&(y|t*8))%256',
'((x>>2)|(y<<2))%256',
'(x*y*t)%256',
'(x+y*t)%256',
'(x^y^(t*16))%256',
'((x*t)&(y*t))%256',
'(x+(y<<(t%4)))%256',
'((x*t%128)^y)%256',
'(x^(y*t*2))%256',
'((x+t)*(y+t))%256',
'(x&y&(t*8))%256',
'((x|t)^(y|t))%256'
];
const vars = ['x', 'y', 't', 'i'];
const ops = ['^', '&', '|', '+', '-', '*', '%'];
const shifts = ['<<', '>>'];
const numbers = ['2', '4', '8', '16', '32', '64', '128', '256'];
const randomChoice = <T>(arr: T[]): T => arr[Math.floor(Math.random() * arr.length)];
const dynamicExpressions = [
() => `${randomChoice(vars)}${randomChoice(ops)}${randomChoice(vars)}`,
() => `(${randomChoice(vars)}${randomChoice(ops)}${randomChoice(vars)})%${randomChoice(numbers)}`,
() => `${randomChoice(vars)}${randomChoice(shifts)}${Math.floor(Math.random() * 8)}`,
() => `(${randomChoice(vars)}*${randomChoice(vars)})%${randomChoice(numbers)}`,
() => `${randomChoice(vars)}^${randomChoice(vars)}^${randomChoice(vars)}`,
];
// 70% chance to pick from presets, 30% chance to generate dynamic
if (Math.random() < 0.7) {
return randomChoice(presets);
} else {
return randomChoice(dynamicExpressions)();
}
}
}