switching

This commit is contained in:
2025-07-06 13:11:19 +02:00
parent f84b515523
commit ec8786ab9b
38 changed files with 9935 additions and 3539 deletions

View File

@ -0,0 +1,327 @@
import { useRef, useEffect } from 'react';
import { useStore } from '@nanostores/react';
import { $appSettings } from '../stores/appSettings';
import { $shader } from '../stores/shader';
import { uiState, showPerformanceWarning } from '../stores/ui';
import {
$input,
updateMousePosition,
updateTouchPosition,
updateDeviceMotion,
} from '../stores/input';
import { FakeShader } from '../FakeShader';
import { UI_HEIGHTS } from '../utils/constants';
export function ShaderCanvas() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const shaderRef = useRef<FakeShader | null>(null);
const settings = useStore($appSettings);
const shader = useStore($shader);
const ui = useStore(uiState);
const input = useStore($input);
// Mouse tracking state
const mouseState = useRef({
lastX: 0,
lastY: 0,
startTime: Date.now(),
});
// Touch gesture state
const touchState = useRef({
initialPinchDistance: 0,
initialPinchAngle: 0,
});
useEffect(() => {
if (!canvasRef.current) return;
// Initialize shader
shaderRef.current = new FakeShader(canvasRef.current, shader.code);
// Set up canvas size
setupCanvas();
// Clean up on unmount
return () => {
if (shaderRef.current) {
shaderRef.current.destroy();
}
};
}, []);
// Update shader when code changes
useEffect(() => {
if (shaderRef.current) {
shaderRef.current.setCode(shader.code);
}
}, [shader.code]);
// Update shader settings
useEffect(() => {
if (shaderRef.current) {
shaderRef.current.setRenderMode(settings.renderMode);
shaderRef.current.setValueMode(settings.valueMode ?? 'integer');
shaderRef.current.setTargetFPS(settings.fps);
}
}, [settings.renderMode, settings.valueMode, settings.fps]);
// Handle canvas resize when resolution or UI visibility changes
useEffect(() => {
setupCanvas();
}, [settings.resolution, ui.uiVisible]);
// Handle animation
useEffect(() => {
if (shaderRef.current) {
const hasTime = shader.code.includes('t');
if (hasTime) {
shaderRef.current.startAnimation();
} else {
shaderRef.current.stopAnimation();
shaderRef.current.render(false);
}
}
}, [shader.code]);
// Update input data to shader
useEffect(() => {
if (shaderRef.current) {
shaderRef.current.setMousePosition(
input.mouseX,
input.mouseY,
input.mousePressed,
input.mouseVX,
input.mouseVY,
input.mouseClickTime
);
shaderRef.current.setTouchPosition(
input.touchCount,
input.touch0X,
input.touch0Y,
input.touch1X,
input.touch1Y,
input.pinchScale,
input.pinchRotation
);
shaderRef.current.setDeviceMotion(
input.accelX,
input.accelY,
input.accelZ,
input.gyroX,
input.gyroY,
input.gyroZ
);
shaderRef.current.setAudioData(
input.audioLevel,
input.bassLevel,
input.midLevel,
input.trebleLevel
);
}
}, [input]);
const setupCanvas = () => {
if (!canvasRef.current) return;
const width = window.innerWidth;
const height = ui.uiVisible
? window.innerHeight - UI_HEIGHTS.TOTAL_UI_HEIGHT
: window.innerHeight;
const scale = settings.resolution;
// Set canvas internal size with resolution scaling
canvasRef.current.width = Math.floor(width / scale);
canvasRef.current.height = Math.floor(height / scale);
console.log(
`Canvas setup: ${canvasRef.current.width}x${canvasRef.current.height} (scale: ${scale}x), UI visible: ${ui.uiVisible}`
);
};
const handleMouseMove = (e: React.MouseEvent) => {
const lastX = mouseState.current.lastX;
const lastY = mouseState.current.lastY;
const x = e.clientX / window.innerWidth;
const y = 1.0 - e.clientY / window.innerHeight; // Invert Y to match shader coordinates
const vx = x - lastX;
const vy = y - lastY;
mouseState.current.lastX = x;
mouseState.current.lastY = y;
updateMousePosition(x, y, input.mousePressed, vx, vy, input.mouseClickTime);
};
const handleMouseDown = () => {
const clickTime = Date.now();
updateMousePosition(
input.mouseX,
input.mouseY,
true,
input.mouseVX,
input.mouseVY,
clickTime
);
};
const handleMouseUp = () => {
updateMousePosition(
input.mouseX,
input.mouseY,
false,
input.mouseVX,
input.mouseVY,
input.mouseClickTime
);
};
const handleTouchStart = (e: React.TouchEvent) => {
// Only prevent default on canvas area for shader interaction
e.preventDefault();
updateTouchPositions(e.touches);
initializePinchGesture(e.touches);
};
const handleTouchMove = (e: React.TouchEvent) => {
e.preventDefault();
updateTouchPositions(e.touches);
updatePinchGesture(e.touches);
};
const handleTouchEnd = (e: React.TouchEvent) => {
e.preventDefault();
const touchCount = e.touches.length;
if (touchCount === 0) {
updateTouchPosition(0, 0, 0, 0, 0, 1, 0);
} else {
updateTouchPositions(e.touches);
}
};
const updateTouchPositions = (touches: React.TouchList | TouchList) => {
let touch0X = 0,
touch0Y = 0,
touch1X = 0,
touch1Y = 0;
if (touches.length > 0) {
touch0X = touches[0].clientX / window.innerWidth;
touch0Y = 1.0 - touches[0].clientY / window.innerHeight;
}
if (touches.length > 1) {
touch1X = touches[1].clientX / window.innerWidth;
touch1Y = 1.0 - touches[1].clientY / window.innerHeight;
}
updateTouchPosition(
touches.length,
touch0X,
touch0Y,
touch1X,
touch1Y,
input.pinchScale,
input.pinchRotation
);
};
const initializePinchGesture = (touches: React.TouchList | TouchList) => {
if (touches.length === 2) {
const dx = touches[1].clientX - touches[0].clientX;
const dy = touches[1].clientY - touches[0].clientY;
touchState.current.initialPinchDistance = Math.sqrt(dx * dx + dy * dy);
touchState.current.initialPinchAngle = Math.atan2(dy, dx);
}
};
const updatePinchGesture = (touches: React.TouchList | TouchList) => {
if (touches.length === 2 && touchState.current.initialPinchDistance > 0) {
const dx = touches[1].clientX - touches[0].clientX;
const dy = touches[1].clientY - touches[0].clientY;
const distance = Math.sqrt(dx * dx + dy * dy);
const angle = Math.atan2(dy, dx);
const pinchScale = distance / touchState.current.initialPinchDistance;
const pinchRotation = angle - touchState.current.initialPinchAngle;
updateTouchPosition(
touches.length,
input.touch0X,
input.touch0Y,
input.touch1X,
input.touch1Y,
pinchScale,
pinchRotation
);
}
};
// Set up device motion listener
useEffect(() => {
const handleDeviceMotion = (e: DeviceMotionEvent) => {
if (e.acceleration && e.rotationRate) {
updateDeviceMotion(
e.acceleration.x || 0,
e.acceleration.y || 0,
e.acceleration.z || 0,
e.rotationRate.alpha || 0,
e.rotationRate.beta || 0,
e.rotationRate.gamma || 0
);
}
};
if (window.DeviceMotionEvent) {
window.addEventListener('devicemotion', handleDeviceMotion);
return () =>
window.removeEventListener('devicemotion', handleDeviceMotion);
}
}, []);
// Set up performance warning listener
useEffect(() => {
const handlePerformanceWarning = (e: MessageEvent) => {
if (e.data === 'performance-warning') {
showPerformanceWarning();
}
};
window.addEventListener('message', handlePerformanceWarning);
return () =>
window.removeEventListener('message', handlePerformanceWarning);
}, []);
// Set up window resize listener
useEffect(() => {
const handleResize = () => setupCanvas();
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [settings.resolution, ui.uiVisible]);
return (
<canvas
ref={canvasRef}
id="canvas"
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
imageRendering: 'pixelated',
touchAction: 'none',
pointerEvents: 'auto',
}}
onMouseMove={handleMouseMove}
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
/>
);
}