Files
canvas-game-starter/lib/u-parametrics-demo.html
2026-04-15 15:21:01 +00:00

765 lines
29 KiB
HTML
Executable File

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>U-Parametrics Demo</title>
<style>
:root {
--space: 8px;
--radius: 5px;
--gray: #6c757d;
--gray-bg: #f5f5f5;
--blue: #007acc;
--green: #28a745;
--white: white;
--dark: #333;
--max-width: 1400px;
--sidebar: 400px;
}
* { box-sizing: border-box; }
body {
font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: var(--max-width);
margin: 0 auto;
padding: var(--space);
background: var(--gray-bg);
}
.container {
background: var(--white);
padding: calc(var(--space) * 1.5);
border-radius: var(--radius);
}
.main-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: calc(var(--space) * 1.5);
}
.api-column {
background: var(--gray-bg);
padding: var(--space);
border-radius: var(--radius);
border-left: 4px solid var(--green);
}
h1 {
color: var(--dark);
text-align: center;
margin-bottom: calc(var(--space) * 1.5);
grid-column: 1 / -1;
}
h2 {
color: var(--gray);
border-bottom: 2px solid #e0e0e0;
padding-bottom: var(--space);
margin-top: calc(var(--space) * 1.5);
}
.demo-section {
margin: var(--space) 0;
padding: var(--space);
background: var(--gray-bg);
border-radius: var(--radius);
border-left: 4px solid var(--blue);
}
.controls, .input-group {
display: flex;
gap: var(--space);
margin: 15px 0;
}
.controls { flex-wrap: wrap; }
.input-group { align-items: center; }
.feature-grid { display: grid; gap: var(--space); margin: var(--space) 0; }
button {
background: var(--blue);
color: var(--white);
border: none;
padding: var(--space) var(--space);
border-radius: var(--radius);
cursor: pointer;
transition: all 0.3s ease;
}
button:hover { filter: brightness(0.9); }
button:disabled { background: #ccc; cursor: not-allowed; }
input[type="text"], input[type="number"], input[type="range"] {
padding: var(--space);
border: 1px solid #ddd;
border-radius: var(--radius);
}
input[type="number"] {
width: 80px;
}
input[type="range"] {
flex: 1;
}
.status, .code-example {
font-family: monospace;
padding: var(--space);
border-radius: var(--radius);
margin: var(--space) 0;
}
.status {
background: var(--dark);
color: #0f0;
white-space: pre-wrap;
max-height: 150px;
overflow-y: auto;
}
.code-example {
background: #2d3748;
color: #e2e8f0;
padding: 15px;
overflow-x: auto;
margin: var(--space) 0;
}
.highlight { color: #68d391; }
.keyword { color: #fbb6ce; }
.string { color: #fbd38d; }
.api-section { margin: 15px 0; }
.api-section h3 {
color: var(--green);
margin: 0 0 var(--space) 0;
}
.api-method {
font-family: monospace;
margin: 3px 0;
color: var(--gray);
}
.api-method .method-name { color: var(--blue); font-weight: bold; }
.api-method .return-type { color: #6f42c1; }
.api-method .param { color: #e83e8c; }
.api-description {
margin: var(--space) 0;
}
.api-options {
color: var(--gray);
}
.help-text {
color: var(--gray);
margin: var(--space) 0;
}
.code-comment {
color: #68d391;
}
.color-box {
display: inline-block;
width: 30px;
height: 30px;
margin: 5px;
border: 1px solid #000;
border-radius: var(--radius);
}
canvas {
border: 1px solid #ddd;
border-radius: var(--radius);
margin: var(--space) 0;
}
.function-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
gap: var(--space);
margin: var(--space) 0;
}
.function-btn {
padding: var(--space);
cursor: pointer;
border: 1px solid #ddd;
text-align: center;
border-radius: var(--radius);
background: var(--white);
transition: all 0.3s ease;
}
.function-btn:hover {
background-color: #f0f0f0;
}
.function-btn.active {
background-color: var(--green);
color: var(--white);
border-color: var(--green);
}
@media (max-width: 1200px) {
.main-layout { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="container">
<h1>U-Parametrics Demo</h1>
<div class="main-layout">
<div class="demo-column">
<h2>Function Visualizer</h2>
<div class="demo-section">
<h3>Interactive Easing Functions</h3>
<div class="function-list" id="function-buttons"></div>
<canvas id="curve-canvas" width="400" height="250"></canvas>
<div class="input-group">
<label>Speed:</label>
<input type="range" id="speed-slider" min="0.5" max="5" step="0.1" value="2">
<span id="speed-value">2.0</span>s
</div>
<div class="controls">
<button onclick="toggleAnimation()" id="anim-btn">Start Animation</button>
<button onclick="resetAnimation()">Reset</button>
</div>
<div class="status" id="function-info"></div>
</div>
</div>
<div class="api-column">
<h2>Core Functions</h2>
<div class="api-section">
<h3>Basic Easing</h3>
<div class="api-method"><span class="method-name">lerp</span>(<span class="param">t, a, b</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Linear interpolation from a to b
</div>
<div class="api-method"><span class="method-name">sin1</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Sine wave easing (0 to 1)
</div>
<div class="api-method"><span class="method-name">flip1</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Inverts the value (1-t)
</div>
</div>
<div class="api-section">
<h3>Smooth Curves</h3>
<div class="api-method"><span class="method-name">smooth_start21</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Quadratic acceleration
</div>
<div class="api-method"><span class="method-name">smooth_start31</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Cubic acceleration
</div>
<div class="api-method"><span class="method-name">parabola1</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Parabolic curve
</div>
</div>
<div class="api-section">
<h3>Examples</h3>
<pre class="code-example">
<span class="keyword">const</span> position = <span class="highlight">lerp</span>(time, startPos, endPos);
<span class="keyword">const</span> alpha = <span class="highlight">sin1</span>(time);
<span class="keyword">const</span> ease = <span class="highlight">smooth_start21</span>(progress);
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Function Comparison</h2>
<div class="demo-section">
<h3>Compare Multiple Functions</h3>
<canvas id="comparison-canvas" width="400" height="300"></canvas>
<div class="controls">
<button onclick="addFunctionToComparison('smooth_start21')">Smooth Start²</button>
<button onclick="addFunctionToComparison('smooth_start31')">Smooth Start³</button>
<button onclick="addFunctionToComparison('parabola1')">Parabola</button>
<button onclick="addFunctionToComparison('sin1')">Sine</button>
<button onclick="clearComparison()">Clear All</button>
</div>
<div class="status" id="comparison-legend"></div>
</div>
</div>
<div class="api-column">
<h2>Advanced Functions</h2>
<div class="api-section">
<h3>Parametric Functions</h3>
<div class="api-method"><span class="method-name">logistic1</span>(<span class="param">t, a</span>) → <span class="return-type">number</span></div>
<div class="api-description">
S-curve with steepness parameter
</div>
<div class="api-method"><span class="method-name">tanh1</span>(<span class="param">t, a</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Hyperbolic tangent curve
</div>
<div class="api-method"><span class="method-name">step1</span>(<span class="param">t, a, b</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Step function with transition range
</div>
</div>
<div class="api-section">
<h3>Complex Functions</h3>
<div class="api-method"><span class="method-name">bezier31</span>(<span class="param">t, b, c</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Cubic Bezier curve
</div>
<div class="api-method"><span class="method-name">bounce_stop1</span>(<span class="param">t</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Bouncing deceleration
</div>
<div class="api-method"><span class="method-name">chain1</span>(<span class="param">t, ...funcs</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Chain multiple functions in sequence
</div>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Animation Examples</h2>
<div class="demo-section">
<h3>Bouncing Ball</h3>
<canvas id="ball-canvas" width="400" height="150"></canvas>
<div class="controls">
<button onclick="animateBall('parabola1')">Parabola</button>
<button onclick="animateBall('smooth_start31')">Cubic</button>
<button onclick="animateBall('bounce_stop1')">Bounce</button>
</div>
</div>
<div class="demo-section">
<h3>Loading Progress</h3>
<canvas id="loading-canvas" width="400" height="50"></canvas>
<div class="controls">
<button onclick="animateLoading('lerp')">Linear</button>
<button onclick="animateLoading('logistic1')">Logistic</button>
<button onclick="animateLoading('smooth_start21')">Acceleration</button>
</div>
</div>
<div class="demo-section">
<h3>Fade Transition</h3>
<canvas id="fade-canvas" width="400" height="100"></canvas>
<div class="controls">
<button onclick="animateFade('sin1')">Sine</button>
<button onclick="animateFade('tanh1')">Tanh</button>
<button onclick="animateFade('smooth_step21')">Smooth Step</button>
</div>
</div>
</div>
<div class="api-column">
<h2>Custom Parameters</h2>
<div class="api-section">
<h3>Parametric Control</h3>
<canvas id="custom-canvas" width="400" height="250"></canvas>
<div class="input-group">
<label>Logistic A:</label>
<input type="range" id="logistic-a" min="1" max="20" step="0.5" value="10" oninput="updateCustomFunction()">
<span id="logistic-a-value">10</span>
</div>
<div class="input-group">
<label>Step Position:</label>
<input type="range" id="step-pos" min="0" max="1" step="0.05" value="0.5" oninput="updateCustomFunction()">
<span id="step-pos-value">0.5</span>
</div>
</div>
<div class="api-section">
<h3>Usage Examples</h3>
<pre class="code-example">
<span class="code-comment">// Custom logistic curve</span>
<span class="keyword">const</span> curve = <span class="highlight">logistic1</span>(t, <span class="string">15</span>);
<span class="code-comment">// Stepped animation</span>
<span class="keyword">const</span> step = <span class="highlight">step1</span>(t, <span class="string">0.3</span>, <span class="string">0.7</span>);
<span class="code-comment">// Bezier timing</span>
<span class="keyword">const</span> ease = <span class="highlight">bezier31</span>(t, <span class="string">0.8</span>, <span class="string">0.2</span>);
</pre>
</div>
</div>
</div>
</div>
<script src="u-parametrics.js"></script>
<script>
// Utility functions for common operations
function getElement(id) {
return document.getElementById(id);
}
function getFloatValue(id) {
return parseFloat(getElement(id).value);
}
function updateTextContent(id, value) {
getElement(id).textContent = value;
}
function getCanvasContext(canvasId) {
const canvas = getElement(canvasId);
const ctx = canvas.getContext('2d');
return { canvas, ctx };
}
function getCanvasDimensions(canvas, margin = 30) {
const width = canvas.width;
const height = canvas.height;
const plotWidth = width - 2 * margin;
const plotHeight = height - 2 * margin;
return { width, height, margin, plotWidth, plotHeight };
}
function clearCanvasWithDimensions(canvasId, margin = 30) {
const { canvas, ctx } = getCanvasContext(canvasId);
const dimensions = getCanvasDimensions(canvas, margin);
ctx.clearRect(0, 0, dimensions.width, dimensions.height);
return { canvas, ctx, ...dimensions };
}
function drawGrid(ctx, dimensions) {
const { width, height, margin, plotWidth, plotHeight } = dimensions;
ctx.strokeStyle = '#f0f0f0';
ctx.lineWidth = 1;
for (let i = 0; i <= 10; i++) {
const x = margin + (i / 10) * plotWidth;
const y = margin + (i / 10) * plotHeight;
// Vertical grid lines
ctx.beginPath();
ctx.moveTo(x, margin);
ctx.lineTo(x, height - margin);
ctx.stroke();
// Horizontal grid lines
ctx.beginPath();
ctx.moveTo(margin, y);
ctx.lineTo(width - margin, y);
ctx.stroke();
}
}
function drawAxes(ctx, dimensions) {
const { width, height, margin } = dimensions;
ctx.strokeStyle = '#333';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(margin, margin);
ctx.lineTo(margin, height - margin);
ctx.lineTo(width - margin, height - margin);
ctx.stroke();
}
function drawAxisLabels(ctx, dimensions) {
const { width, height, margin } = dimensions;
ctx.fillStyle = '#333';
ctx.font = '12px Arial';
ctx.fillText('0', margin - 15, height - margin + 5);
ctx.fillText('1', margin - 15, margin + 5);
ctx.fillText('0', margin - 5, height - margin + 15);
ctx.fillText('1', width - margin - 5, height - margin + 15);
}
function drawFunctionCurve(ctx, dimensions, func, color = '#e74c3c', lineWidth = 3) {
const { margin, plotWidth, plotHeight, height } = dimensions;
ctx.strokeStyle = color;
ctx.lineWidth = lineWidth;
ctx.beginPath();
for (let i = 0; i <= plotWidth; i++) {
const t = i / plotWidth;
const value = clamp1(func(t));
const x = margin + i;
const y = height - margin - value * plotHeight;
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.stroke();
}
function setupPlotCanvas(canvasId, margin = 30) {
const { canvas, ctx, ...dimensions } = clearCanvasWithDimensions(canvasId, margin);
drawGrid(ctx, { ...dimensions, margin });
drawAxes(ctx, { ...dimensions, margin });
return { canvas, ctx, ...dimensions };
}
function createAnimationLoop(canvasId, animationFunction, duration = 1, step = 0.01) {
const { canvas, ctx } = getCanvasContext(canvasId);
let t = 0;
function frame() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
animationFunction(ctx, canvas, t);
t += step;
if (t <= duration) {
requestAnimationFrame(frame);
}
}
frame();
}
const easingFunctions = {
'lerp': { func: (t) => lerp(t), name: 'Linear', desc: 'Constant rate of change' },
'sin1': { func: sin1, name: 'Sine', desc: 'Smooth start and end' },
'smooth_start21': { func: smooth_start21, name: 'Smooth Start²', desc: 'Slow start, fast end' },
'smooth_start31': { func: smooth_start31, name: 'Smooth Start³', desc: 'Very slow start, very fast end' },
'parabola1': { func: parabola1, name: 'Parabola', desc: 'Fast start and end, slow middle' },
'flip1': { func: flip1, name: 'Flip', desc: 'Inverts the value (1-t)' },
'step1': { func: (t) => step1(t), name: 'Step', desc: 'Sharp transition at midpoint' },
'logistic1': { func: (t) => logistic1(t), name: 'Logistic', desc: 'S-curve transition' },
'tanh1': { func: (t) => tanh1(t), name: 'Tanh', desc: 'Smooth S-curve' },
'bounce_stop1': { func: bounce_stop1, name: 'Bounce', desc: 'Bouncing deceleration' },
'smooth_step21': { func: smooth_step21, name: 'Smooth Step²', desc: 'Smooth acceleration and deceleration' },
'bezier31': { func: (t) => bezier31(t), name: 'Bezier³', desc: 'Cubic Bezier curve' }
};
let currentFunction = 'sin1';
let animationRunning = false;
let animationTime = 0;
let comparisonFunctions = [];
const colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#feca57', '#ff9ff3', '#54a0ff'];
function log(message, outputId = 'function-info') {
const output = getElement(outputId);
const timestamp = new Date().toLocaleTimeString();
output.textContent += `[${timestamp}] ${message}\n`;
output.scrollTop = output.scrollHeight;
}
function clearOutput(outputId) {
getElement(outputId).textContent = '';
}
function initializeFunctionButtons() {
const container = getElement('function-buttons');
Object.keys(easingFunctions).forEach(key => {
const btn = document.createElement('div');
btn.className = `function-btn ${key === currentFunction ? 'active' : ''}`;
btn.textContent = easingFunctions[key].name;
btn.onclick = () => selectFunction(key);
btn.id = `btn-${key}`;
container.appendChild(btn);
});
}
function selectFunction(funcName) {
document.querySelectorAll('.function-btn').forEach(btn => btn.classList.remove('active'));
getElement(`btn-${funcName}`).classList.add('active');
currentFunction = funcName;
drawCurve();
updateFunctionInfo();
}
function updateFunctionInfo() {
clearOutput('function-info');
const info = easingFunctions[currentFunction];
log(`${info.name}: ${info.desc}`, 'function-info');
}
function drawCurve() {
const { canvas, ctx, ...dimensions } = setupPlotCanvas('curve-canvas');
const func = easingFunctions[currentFunction].func;
// Draw the main function curve
drawFunctionCurve(ctx, dimensions, func);
// Draw animation point if running
if (animationRunning) {
const t = (animationTime % 1);
const value = clamp1(func(t));
const x = dimensions.margin + t * dimensions.plotWidth;
const y = dimensions.height - dimensions.margin - value * dimensions.plotHeight;
ctx.fillStyle = '#3498db';
ctx.beginPath();
ctx.arc(x, y, 6, 0, 2 * Math.PI);
ctx.fill();
}
drawAxisLabels(ctx, dimensions);
}
function toggleAnimation() {
const btn = getElement('anim-btn');
if (animationRunning) {
animationRunning = false;
btn.textContent = 'Start Animation';
} else {
animationRunning = true;
btn.textContent = 'Stop Animation';
animate();
}
}
function resetAnimation() {
animationTime = 0;
drawCurve();
}
function animate() {
if (!animationRunning) return;
const speed = getFloatValue('speed-slider');
animationTime += 0.016 * speed;
drawCurve();
requestAnimationFrame(animate);
}
getElement('speed-slider').oninput = function() {
updateTextContent('speed-value', this.value);
};
function addFunctionToComparison(funcName) {
if (!comparisonFunctions.includes(funcName)) {
comparisonFunctions.push(funcName);
drawComparison();
updateComparisonLegend();
}
}
function clearComparison() {
comparisonFunctions = [];
clearOutput('comparison-legend');
drawComparison();
}
function drawComparison() {
const { canvas, ctx, ...dimensions } = setupPlotCanvas('comparison-canvas');
comparisonFunctions.forEach((funcName, index) => {
const func = easingFunctions[funcName].func;
const color = colors[index % colors.length];
drawFunctionCurve(ctx, dimensions, func, color);
});
}
function updateComparisonLegend() {
clearOutput('comparison-legend');
comparisonFunctions.forEach((funcName, index) => {
const color = colors[index % colors.length];
const name = easingFunctions[funcName].name;
log(`${name}`, 'comparison-legend');
});
}
function animateBall(easingFunc) {
const func = easingFunctions[easingFunc].func;
createAnimationLoop('ball-canvas', (ctx, canvas, t) => {
const value = func(t);
const x = 20 + (canvas.width - 40) * t;
const y = canvas.height - 20 - value * (canvas.height - 40);
ctx.fillStyle = '#e74c3c';
ctx.beginPath();
ctx.arc(x, y, 10, 0, 2 * Math.PI);
ctx.fill();
});
}
function animateLoading(easingFunc) {
const func = easingFunctions[easingFunc].func;
createAnimationLoop('loading-canvas', (ctx, canvas, t) => {
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(10, 15, canvas.width - 20, 20);
const progress = func(t);
ctx.fillStyle = '#4ecdc4';
ctx.fillRect(10, 15, progress * (canvas.width - 20), 20);
});
}
function animateFade(easingFunc) {
const func = easingFunctions[easingFunc].func;
createAnimationLoop('fade-canvas', (ctx, canvas, t) => {
const alpha = func(t);
ctx.fillStyle = `rgba(52, 152, 219, ${alpha})`;
ctx.fillRect(50, 25, 100, 50);
ctx.fillStyle = '#333';
ctx.font = '12px Arial';
ctx.fillText(`Alpha: ${alpha.toFixed(2)}`, 10, 90);
});
}
function updateCustomFunction() {
const aValue = getFloatValue('logistic-a');
const stepPos = getFloatValue('step-pos');
updateTextContent('logistic-a-value', aValue);
updateTextContent('step-pos-value', stepPos);
drawCustomFunctions(aValue, stepPos);
}
function drawCustomFunctions(aValue, stepPos) {
const { canvas, ctx, ...dimensions } = setupPlotCanvas('custom-canvas');
// Draw logistic function with custom parameter
drawFunctionCurve(ctx, dimensions, (t) => logistic1(t, aValue), '#e74c3c');
// Draw step function with custom position
drawFunctionCurve(ctx, dimensions, (t) => step1(t, stepPos), '#3498db');
// Draw legend
ctx.fillStyle = '#e74c3c';
ctx.font = '12px Arial';
ctx.fillText(`Logistic (a=${aValue})`, dimensions.margin, dimensions.height - 10);
ctx.fillStyle = '#3498db';
ctx.fillText(`Step (pos=${stepPos})`, dimensions.margin + 150, dimensions.height - 10);
}
document.addEventListener('DOMContentLoaded', function() {
initializeFunctionButtons();
drawCurve();
updateFunctionInfo();
drawComparison();
updateCustomFunction();
});
</script>
</body>
</html>