Files
2026-04-15 15:21:01 +00:00

1097 lines
47 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-Helper 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;
}
@media (max-width: 1200px) {
.main-layout { grid-template-columns: 1fr; }
}
</style>
</head>
<body>
<div class="container">
<h1>U-Helper Demo</h1>
<div class="main-layout">
<div class="demo-column">
<h2>Hash Generation & Utility</h2>
<div class="demo-section">
<div class="input-group">
<input type="text" id="hash-input" value="Hello World" placeholder="Enter text">
<button onclick="generateHash()">Generate Hash</button>
</div>
<div class="status" id="hash-output"></div>
</div>
<div class="demo-section">
<div class="controls">
<button onclick="demonstrateFirst()">Test first()</button>
<button onclick="demonstrateIsset()">Test isset()</button>
</div>
<div class="status" id="utility-output"></div>
</div>
</div>
<div class="api-column">
<h2>Overview</h2>
<div class="api-section">
<div class="api-method"><span class="method-name">make_hash</span>(<span class="param">string</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Generates a unique hash value from any input string for use as identifiers or keys.
</div>
<div class="api-method"><span class="method-name">first</span>(<span class="param">...args</span>) → <span class="return-type">any</span></div>
<div class="api-description">
Returns the first non-empty value from arguments<br>
</div>
<div class="api-method"><span class="method-name">isset</span>(<span class="param">value</span>) → <span class="return-type">boolean</span></div>
<div class="api-description">
Checks if a value is defined (not undefined)
</div>
</div>
<div class="api-section">
<h3>Examples</h3>
<pre class="code-example">
<span class="code-comment">// Hash generation</span>
<span class="keyword">const</span> hash = <span class="highlight">make_hash</span>(<span class="string">'player_123'</span>);
<span class="code-comment">// Get first valid value</span>
<span class="keyword">const</span> name = <span class="highlight">first</span>(<span class="string">''</span>, <span class="keyword">null</span>, <span class="string">'Player'</span>);
<span class="code-comment">// Check if defined</span>
<span class="keyword">if</span> (<span class="highlight">isset</span>(config.debug)) {
console.log(<span class="string">'Debug mode'</span>);
}
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Color Functions</h2>
<div class="demo-section">
<h3>RGB Color Generation</h3>
<div class="input-group">
<label>R:</label>
<input type="number" id="r-input" value="255" min="0" max="255">
<label>G:</label>
<input type="number" id="g-input" value="128" min="0" max="255">
<label>B:</label>
<input type="number" id="b-input" value="64" min="0" max="255">
</div>
<div class="controls">
<button onclick="demonstrateColors()">Generate Color</button>
</div>
<div class="status" id="color-output"></div>
</div>
</div>
<div class="api-column">
<h2>Colors</h2>
<div class="api-section">
<h3>Color Functions</h3>
<div class="api-method"><span class="method-name">rgb</span>(<span class="param">r, g, b</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Converts RGB values (0-255) to color integer<br>
</div>
<div class="api-method"><span class="method-name">frgb</span>(<span class="param">r, g, b</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Converts floating RGB values (0.0-1.0) to color integer
</div>
</div>
<div class="api-section">
<h3>Usage Examples</h3>
<pre class="code-example">
<span class="code-comment">// Integer RGB values</span>
<span class="keyword">const</span> red = <span class="highlight">rgb</span>(<span class="string">255</span>, <span class="string">0</span>, <span class="string">0</span>);
<span class="code-comment">// Floating point RGB values</span>
<span class="keyword">const</span> blue = <span class="highlight">frgb</span>(<span class="string">0.0</span>, <span class="string">0.0</span>, <span class="string">1.0</span>);
<span class="code-comment">// Use in graphics contexts</span>
ctx.fillStyle = <span class="string">'#'</span> + red.toString(<span class="string">16</span>);
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Mathematical Functions</h2>
<div class="demo-section">
<h3>Linear Interpolation</h3>
<div class="input-group">
<label>From:</label>
<input type="number" id="lerp-a" value="0" step="0.1">
<label>To:</label>
<input type="number" id="lerp-b" value="100" step="0.1">
<label>T:</label>
<input type="number" id="lerp-t" value="0.5" min="0" max="1" step="0.1">
</div>
<div class="controls">
<button onclick="demonstrateLerp()">Calculate Lerp</button>
</div>
<div class="status" id="lerp-output"></div>
</div>
<div class="demo-section">
<h3>Value Clamping</h3>
<div class="input-group">
<label>Value:</label>
<input type="number" id="clamp-val" value="150">
<label>Min:</label>
<input type="number" id="clamp-min" value="0">
<label>Max:</label>
<input type="number" id="clamp-max" value="100">
</div>
<div class="controls">
<button onclick="demonstrateClamp()">Clamp Value</button>
</div>
<div class="status" id="clamp-output"></div>
</div>
</div>
<div class="api-column">
<h2>Math</h2>
<div class="api-section">
<h3>Mathematical Functions</h3>
<div class="api-method"><span class="method-name">lerp</span>(<span class="param">a, b, t</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Linear interpolation between two values<br>
</div>
<div class="api-method"><span class="method-name">clamp</span>(<span class="param">value, min, max</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Constrains value between min and max<br>
</div>
<div class="api-method"><span class="method-name">dist</span>(<span class="param">x1, y1, x2, y2</span>) → <span class="return-type">number</span></div>
<div class="api-description">
Calculates Euclidean distance between points
</div>
</div>
<div class="api-section">
<h3>Math Examples</h3>
<pre class="code-example">
<span class="code-comment">// Smooth transitions</span>
<span class="keyword">const</span> position = <span class="highlight">lerp</span>(startPos, endPos, time);
<span class="code-comment">// Keep values in bounds</span>
<span class="keyword">const</span> health = <span class="highlight">clamp</span>(currentHealth, <span class="string">0</span>, <span class="string">100</span>);
<span class="code-comment">// Calculate distances</span>
<span class="keyword">const</span> distance = <span class="highlight">dist</span>(player.x, player.y, enemy.x, enemy.y);
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Geometry</h2>
<div class="demo-section">
<h3>Bresenham Line Algorithm</h3>
<canvas id="line-canvas" width="400" height="200"></canvas>
<div class="input-group">
<label>Start:</label>
<input type="number" id="line-x0" value="10" placeholder="X">
<input type="number" id="line-y0" value="10" placeholder="Y">
<label>End:</label>
<input type="number" id="line-x1" value="350" placeholder="X">
<input type="number" id="line-y1" value="150" placeholder="Y">
</div>
<div class="controls">
<button onclick="drawBresenhamLine()">Draw Line</button>
</div>
</div>
<div class="demo-section">
<h3>Lerp</h3>
<canvas id="lerp-canvas" width="400" height="100"></canvas>
<div class="input-group">
<input type="range" id="lerp-slider" min="0" max="1" step="0.01" value="0.5" oninput="updateLerpVisualization()">
<span>T: <span id="t-value">0.5</span></span>
</div>
</div>
</div>
<div class="api-column">
<h2>Geometry</h2>
<div class="api-section">
<h3>Line Generation</h3>
<div class="api-method"><span class="method-name">bresenham_line</span>(<span class="param">x0, y0, x1, y1</span>) → <span class="return-type">Array</span></div>
<div class="api-description">
Generates pixel-perfect line points using Bresenham's algorithm. Returns array of [x, y] coordinate pairs.
</div>
</div>
<div class="api-section">
<pre class="code-example">
<span class="code-comment">// Generate line for raycast</span>
<span class="keyword">const</span> line = <span class="highlight">bresenham_line</span>(startX, startY, endX, endY);
line.forEach(([x, y]) => {
<span class="keyword">if</span> (hitWall(x, y)) <span class="keyword">return</span>;
drawPixel(x, y);
});
<span class="code-comment">// Check collision along path</span>
<span class="keyword">const</span> path = <span class="highlight">bresenham_line</span>(from.x, from.y, to.x, to.y);
<span class="keyword">const</span> blocked = path.some(([x, y]) => grid[y][x]);
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Vector Operations</h2>
<div class="demo-section">
<div class="input-group">
<label>Vector 1:</label>
<input type="number" id="v1x" value="3" step="0.1">
<input type="number" id="v1y" value="4" step="0.1">
</div>
<div class="input-group">
<label>Vector 2:</label>
<input type="number" id="v2x" value="1" step="0.1">
<input type="number" id="v2y" value="0" step="0.1">
</div>
<div class="controls">
<button onclick="demonstrateVectors()">Calculate</button>
<button onclick="demonstrateVectorRotation()">Rotate V1</button>
</div>
<div class="status" id="vector-output"></div>
</div>
<div class="demo-section">
<canvas id="vector-canvas" width="400" height="300"></canvas>
<div class="input-group">
<label>Rotation:</label>
<input type="range" id="rotation-slider" min="0" max="6.28" step="0.1" value="0" oninput="updateVectorVisualization()">
<span>Angle: <span id="angle-value">0°</span></span>
</div>
</div>
</div>
<div class="api-column">
<h2>Vectors</h2>
<div class="api-section">
<div class="api-method"><span class="method-name">normalize</span>(<span class="param">x, y</span>) → <span class="return-type">Array[2]</span></div>
<div class="api-description">Returns unit vector with length 1</div>
<div class="api-method"><span class="method-name">dot</span>(<span class="param">x1, y1, x2, y2</span>) → <span class="return-type">number</span></div>
<div class="api-description">Dot product of two vectors</div>
<div class="api-method"><span class="method-name">cross</span>(<span class="param">x1, y1, x2, y2</span>) → <span class="return-type">number</span></div>
<div class="api-description">Cross product (scalar for 2D)</div>
<div class="api-method"><span class="method-name">magnitude</span>(<span class="param">x, y</span>) → <span class="return-type">number</span></div>
<div class="api-description">Length of vector</div>
<div class="api-method"><span class="method-name">angle_between_vectors</span>(<span class="param">x1, y1, x2, y2</span>) → <span class="return-type">number</span></div>
<div class="api-description">Angle between vectors in radians</div>
<div class="api-method"><span class="method-name">rotate_vector</span>(<span class="param">x, y, angle</span>) → <span class="return-type">Array[2]</span></div>
<div class="api-description">Rotate vector by angle (radians)</div>
</div>
<div class="api-section">
<h3>Vector Examples</h3>
<pre class="code-example">
<span class="code-comment">// Normalize movement vector</span>
<span class="keyword">const</span> [dx, dy] = <span class="highlight">normalize</span>(inputX, inputY);
player.x += dx * speed;
<span class="code-comment">// Check if vectors point same direction</span>
<span class="keyword">const</span> dotProduct = <span class="highlight">dot</span>(v1x, v1y, v2x, v2y);
<span class="keyword">const</span> sameDirection = dotProduct > <span class="string">0</span>;
<span class="code-comment">// Rotate sprite direction</span>
<span class="keyword">const</span> [newX, newY] = <span class="highlight">rotate_vector</span>(dirX, dirY, angle);
</pre>
</div>
</div>
</div>
<div class="main-layout">
<div class="demo-column">
<h2>Collision Detection</h2>
<div class="demo-section">
<h3>Interactive Collision Testing</h3>
<canvas id="collision-canvas" width="400" height="300"></canvas>
<div class="controls">
<button onclick="toggleCollisionMode('rect')">Rectangle Mode</button>
<button onclick="toggleCollisionMode('circle')">Circle Mode</button>
<button onclick="clearCollisionCanvas()">Clear</button>
</div>
<div class="status" id="collision-output"></div>
<div class="help-text">Click and drag to create shapes. Watch for collision detection!</div>
</div>
<div class="demo-section">
<h3>Collision Calculator</h3>
<div class="input-group">
<label>Circle:</label>
<input type="number" id="circle-x" value="200" placeholder="X">
<input type="number" id="circle-y" value="150" placeholder="Y">
<input type="number" id="circle-r" value="50" placeholder="Radius">
</div>
<div class="input-group">
<label>Rect:</label>
<input type="number" id="rect-x" value="150" placeholder="X">
<input type="number" id="rect-y" value="100" placeholder="Y">
<input type="number" id="rect-w" value="100" placeholder="Width">
<input type="number" id="rect-h" value="100" placeholder="Height">
</div>
<div class="controls">
<button onclick="testCollisions()">Test Collisions</button>
</div>
</div>
</div>
<div class="api-column">
<h2>Collision</h2>
<div class="api-section">
<h3>Collision Functions</h3>
<div class="api-method"><span class="method-name">point_in_rect</span>(<span class="param">px, py, rx, ry, rw, rh</span>) → <span class="return-type">boolean</span></div>
<div class="api-description">Check if point is inside rectangle</div>
<div class="api-method"><span class="method-name">point_in_circle</span>(<span class="param">px, py, cx, cy, radius</span>) → <span class="return-type">boolean</span></div>
<div class="api-description">Check if point is inside circle</div>
<div class="api-method"><span class="method-name">rect_overlap</span>(<span class="param">r1x, r1y, r1w, r1h, r2x, r2y, r2w, r2h</span>) → <span class="return-type">boolean</span></div>
<div class="api-description">Check if two rectangles overlap</div>
<div class="api-method"><span class="method-name">circle_overlap</span>(<span class="param">c1x, c1y, r1, c2x, c2y, r2</span>) → <span class="return-type">boolean</span></div>
<div class="api-description">Check if two circles overlap</div>
<div class="api-method"><span class="method-name">circle_rect_overlap</span>(<span class="param">cx, cy, radius, rx, ry, rw, rh</span>) → <span class="return-type">boolean</span></div>
<div class="api-description">Check if circle and rectangle overlap</div>
<div class="api-method"><span class="method-name">line_circle_intersect</span>(<span class="param">x1, y1, x2, y2, cx, cy, radius</span>) → <span class="return-type">boolean</span></div>
<div class="api-description">Check if line intersects circle</div>
</div>
<div class="api-section">
<h3>Collision Examples</h3>
<pre class="code-example">
<span class="code-comment">// Mouse click detection</span>
<span class="keyword">if</span> (<span class="highlight">point_in_rect</span>(mouseX, mouseY, button.x, button.y, button.w, button.h)) {
button.onClick();
}
<span class="code-comment">// Enemy collision</span>
<span class="keyword">if</span> (<span class="highlight">circle_overlap</span>(player.x, player.y, player.radius,
enemy.x, enemy.y, enemy.radius)) {
takeDamage();
}
<span class="code-comment">// Projectile vs wall</span>
<span class="keyword">if</span> (<span class="highlight">line_circle_intersect</span>(bullet.prevX, bullet.prevY,
bullet.x, bullet.y, wall.x, wall.y, wall.radius)) {
bullet.explode();
}
</pre>
</div>
</div>
</div>
</div>
<script src="u-helper.js"></script>
<script>
function log(message, outputId = 'hash-output') {
const output = document.getElementById(outputId);
const timestamp = new Date().toLocaleTimeString();
output.textContent += `[${timestamp}] ${message}\n`;
output.scrollTop = output.scrollHeight;
}
function clearOutput(outputId) {
document.getElementById(outputId).textContent = '';
}
function generateHash() {
clearOutput('hash-output');
const input = document.getElementById('hash-input').value;
const hash = make_hash(input);
log(`Hash of "${input}": ${hash}`, 'hash-output');
}
function demonstrateFirst() {
clearOutput('utility-output');
const result = first("", null, undefined, "Hello", "World");
log(`first("", null, undefined, "Hello", "World")`, 'utility-output');
log(`Result: "${result}"`, 'utility-output');
}
function demonstrateIsset() {
const tests = [
{ value: undefined, name: 'undefined' },
{ value: null, name: 'null' },
{ value: "", name: '""' },
{ value: 0, name: '0' },
{ value: false, name: 'false' },
{ value: "hello", name: '"hello"' }
];
tests.forEach(test => {
const result = isset(test.value);
log(`isset(${test.name}) = ${result}`, 'utility-output');
});
}
function demonstrateColors() {
clearOutput('color-output');
const r = parseInt(document.getElementById('r-input').value);
const g = parseInt(document.getElementById('g-input').value);
const b = parseInt(document.getElementById('b-input').value);
const rgbValue = rgb(r, g, b);
const frgbValue = frgb(r/255, g/255, b/255);
const hexColor = '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');
log(`RGB values: (${r}, ${g}, ${b})`, 'color-output');
log(`rgb() result: ${rgbValue}`, 'color-output');
log(`frgb() result: ${frgbValue}`, 'color-output');
log(`Hex equivalent: ${hexColor}`, 'color-output');
const colorBox = document.createElement('div');
colorBox.className = 'color-box';
colorBox.style.backgroundColor = hexColor;
document.getElementById('color-output').appendChild(colorBox);
}
function demonstrateLerp() {
clearOutput('lerp-output');
const a = parseFloat(document.getElementById('lerp-a').value);
const b = parseFloat(document.getElementById('lerp-b').value);
const t = parseFloat(document.getElementById('lerp-t').value);
const result = lerp(a, b, t);
log(`lerp(${a}, ${b}, ${t})`, 'lerp-output');
log(`Result: ${result.toFixed(2)}`, 'lerp-output');
}
function demonstrateClamp() {
clearOutput('clamp-output');
const val = parseFloat(document.getElementById('clamp-val').value);
const min = parseFloat(document.getElementById('clamp-min').value);
const max = parseFloat(document.getElementById('clamp-max').value);
const result = clamp(val, min, max);
log(`clamp(${val}, ${min}, ${max})`, 'clamp-output');
log(`Result: ${result}`, 'clamp-output');
}
function calculateDistance() {
clearOutput('distance-output');
const x1 = parseFloat(document.getElementById('p1x').value);
const y1 = parseFloat(document.getElementById('p1y').value);
const x2 = parseFloat(document.getElementById('p2x').value);
const y2 = parseFloat(document.getElementById('p2y').value);
const distance = dist(x1, y1, x2, y2);
log(`Distance between (${x1}, ${y1}) and (${x2}, ${y2})`, 'distance-output');
log(`Result: ${distance.toFixed(2)}`, 'distance-output');
}
function drawBresenhamLine() {
const canvas = document.getElementById('line-canvas');
const ctx = canvas.getContext('2d');
const x0 = parseInt(document.getElementById('line-x0').value);
const y0 = parseInt(document.getElementById('line-y0').value);
const x1 = parseInt(document.getElementById('line-x1').value);
const y1 = parseInt(document.getElementById('line-y1').value);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = '#f0f0f0';
ctx.lineWidth = 1;
for (let x = 0; x < canvas.width; x += 10) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
for (let y = 0; y < canvas.height; y += 10) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
const points = bresenham_line(x0, y0, x1, y1);
ctx.fillStyle = 'red';
points.forEach(([x, y]) => {
ctx.fillRect(x - 1, y - 1, 3, 3);
});
ctx.fillStyle = 'green';
ctx.fillRect(x0 - 3, y0 - 3, 6, 6);
ctx.fillStyle = 'blue';
ctx.fillRect(x1 - 3, y1 - 3, 6, 6);
}
function updateLerpVisualization() {
const slider = document.getElementById('lerp-slider');
const t = parseFloat(slider.value);
document.getElementById('t-value').textContent = t.toFixed(2);
const canvas = document.getElementById('lerp-canvas');
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
const startX = 50, startY = 50;
const endX = 350, endY = 50;
const currentX = lerp(startX, endX, t);
const currentY = lerp(startY, endY, t);
ctx.strokeStyle = '#ccc';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.stroke();
ctx.fillStyle = 'green';
ctx.beginPath();
ctx.arc(startX, startY, 8, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(endX, endY, 8, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(currentX, currentY, 6, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = 'black';
ctx.font = '12px Arial';
ctx.fillText('Start', startX - 20, startY - 15);
ctx.fillText('End', endX - 10, endY - 15);
ctx.fillText(`t=${t.toFixed(2)}`, currentX - 20, currentY - 15);
}
document.addEventListener('DOMContentLoaded', function() {
generateHash();
demonstrateColors();
updateLerpVisualization();
initializeCollisionCanvas();
});
// Vector operations demo functions
function demonstrateVectors() {
clearOutput('vector-output');
const v1x = parseFloat(document.getElementById('v1x').value);
const v1y = parseFloat(document.getElementById('v1y').value);
const v2x = parseFloat(document.getElementById('v2x').value);
const v2y = parseFloat(document.getElementById('v2y').value);
// Calculate various vector operations
const mag1 = magnitude(v1x, v1y);
const mag2 = magnitude(v2x, v2y);
const [norm1x, norm1y] = normalize(v1x, v1y);
const [norm2x, norm2y] = normalize(v2x, v2y);
const dotProduct = dot(v1x, v1y, v2x, v2y);
const crossProduct = cross(v1x, v1y, v2x, v2y);
const angle = angle_between_vectors(v1x, v1y, v2x, v2y);
log(`Vector 1: (${v1x}, ${v1y})`, 'vector-output');
log(`Vector 2: (${v2x}, ${v2y})`, 'vector-output');
log(`Magnitude 1: ${mag1.toFixed(3)}`, 'vector-output');
log(`Magnitude 2: ${mag2.toFixed(3)}`, 'vector-output');
log(`Normalized 1: (${norm1x.toFixed(3)}, ${norm1y.toFixed(3)})`, 'vector-output');
log(`Normalized 2: (${norm2x.toFixed(3)}, ${norm2y.toFixed(3)})`, 'vector-output');
log(`Dot Product: ${dotProduct.toFixed(3)}`, 'vector-output');
log(`Cross Product: ${crossProduct.toFixed(3)}`, 'vector-output');
log(`Angle between: ${(angle * 180 / Math.PI).toFixed(1)}°`, 'vector-output');
}
function demonstrateVectorRotation() {
clearOutput('vector-output');
const v1x = parseFloat(document.getElementById('v1x').value);
const v1y = parseFloat(document.getElementById('v1y').value);
const angles = [0, Math.PI/4, Math.PI/2, Math.PI, 3*Math.PI/2];
const angleNames = ['0°', '45°', '90°', '180°', '270°'];
log(`Original vector: (${v1x}, ${v1y})`, 'vector-output');
angles.forEach((angle, i) => {
const [rotX, rotY] = rotate_vector(v1x, v1y, angle);
log(`Rotated ${angleNames[i]}: (${rotX.toFixed(3)}, ${rotY.toFixed(3)})`, 'vector-output');
});
}
function updateVectorVisualization() {
const slider = document.getElementById('rotation-slider');
const angle = parseFloat(slider.value);
document.getElementById('angle-value').textContent = (angle * 180 / Math.PI).toFixed(0) + '°';
const canvas = document.getElementById('vector-canvas');
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const scale = 30;
// Draw grid
ctx.strokeStyle = '#f0f0f0';
ctx.lineWidth = 1;
for (let x = 0; x < canvas.width; x += 20) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
for (let y = 0; y < canvas.height; y += 20) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
// Draw axes
ctx.strokeStyle = '#ccc';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(0, centerY);
ctx.lineTo(canvas.width, centerY);
ctx.moveTo(centerX, 0);
ctx.lineTo(centerX, canvas.height);
ctx.stroke();
// Original vector
const origX = 3, origY = 2;
drawVector(ctx, centerX, centerY, origX * scale, -origY * scale, 'blue', 'Original');
// Rotated vector
const [rotX, rotY] = rotate_vector(origX, origY, angle);
drawVector(ctx, centerX, centerY, rotX * scale, -rotY * scale, 'red', 'Rotated');
}
function drawVector(ctx, startX, startY, dx, dy, color, label) {
const endX = startX + dx;
const endY = startY + dy;
// Draw vector line
ctx.strokeStyle = color;
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.stroke();
// Draw arrowhead
const angle = Math.atan2(dy, dx);
const arrowLength = 15;
const arrowAngle = Math.PI / 6;
ctx.beginPath();
ctx.moveTo(endX, endY);
ctx.lineTo(endX - arrowLength * Math.cos(angle - arrowAngle),
endY - arrowLength * Math.sin(angle - arrowAngle));
ctx.moveTo(endX, endY);
ctx.lineTo(endX - arrowLength * Math.cos(angle + arrowAngle),
endY - arrowLength * Math.sin(angle + arrowAngle));
ctx.stroke();
// Label
ctx.fillStyle = color;
ctx.font = '12px Arial';
ctx.fillText(label, endX + 5, endY - 5);
}
// Collision detection demo functions
let collisionMode = 'rect';
let collisionShapes = [];
let isDrawing = false;
let startPos = {x: 0, y: 0};
function initializeCollisionCanvas() {
const canvas = document.getElementById('collision-canvas');
canvas.addEventListener('mousedown', startCollisionDraw);
canvas.addEventListener('mousemove', updateCollisionDraw);
canvas.addEventListener('mouseup', endCollisionDraw);
updateCollisionVisualization();
}
function toggleCollisionMode(mode) {
collisionMode = mode;
clearOutput('collision-output');
log(`Switched to ${mode} mode`, 'collision-output');
}
function startCollisionDraw(e) {
const rect = e.target.getBoundingClientRect();
startPos.x = e.clientX - rect.left;
startPos.y = e.clientY - rect.top;
isDrawing = true;
}
function updateCollisionDraw(e) {
if (!isDrawing) return;
const rect = e.target.getBoundingClientRect();
const currentX = e.clientX - rect.left;
const currentY = e.clientY - rect.top;
updateCollisionVisualization(currentX, currentY);
}
function endCollisionDraw(e) {
if (!isDrawing) return;
const rect = e.target.getBoundingClientRect();
const endX = e.clientX - rect.left;
const endY = e.clientY - rect.top;
if (collisionMode === 'rect') {
const width = Math.abs(endX - startPos.x);
const height = Math.abs(endY - startPos.y);
const x = Math.min(startPos.x, endX);
const y = Math.min(startPos.y, endY);
collisionShapes.push({
type: 'rect',
x: x, y: y, w: width, h: height
});
} else if (collisionMode === 'circle') {
const radius = dist(startPos.x, startPos.y, endX, endY);
collisionShapes.push({
type: 'circle',
x: startPos.x, y: startPos.y, r: radius
});
}
isDrawing = false;
updateCollisionVisualization();
checkCollisions();
}
function updateCollisionVisualization(mouseX, mouseY) {
const canvas = document.getElementById('collision-canvas');
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw existing shapes
collisionShapes.forEach((shape, index) => {
if (shape.type === 'rect') {
ctx.strokeStyle = 'blue';
ctx.lineWidth = 2;
ctx.strokeRect(shape.x, shape.y, shape.w, shape.h);
ctx.fillStyle = 'rgba(0, 0, 255, 0.1)';
ctx.fillRect(shape.x, shape.y, shape.w, shape.h);
} else if (shape.type === 'circle') {
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(shape.x, shape.y, shape.r, 0, 2 * Math.PI);
ctx.stroke();
ctx.fillStyle = 'rgba(255, 0, 0, 0.1)';
ctx.fill();
}
});
// Draw current drawing
if (isDrawing && mouseX !== undefined && mouseY !== undefined) {
ctx.strokeStyle = 'gray';
ctx.lineWidth = 1;
ctx.setLineDash([5, 5]);
if (collisionMode === 'rect') {
const width = mouseX - startPos.x;
const height = mouseY - startPos.y;
ctx.strokeRect(startPos.x, startPos.y, width, height);
} else if (collisionMode === 'circle') {
const radius = dist(startPos.x, startPos.y, mouseX, mouseY);
ctx.beginPath();
ctx.arc(startPos.x, startPos.y, radius, 0, 2 * Math.PI);
ctx.stroke();
}
ctx.setLineDash([]);
}
}
function checkCollisions() {
clearOutput('collision-output');
let collisionCount = 0;
for (let i = 0; i < collisionShapes.length; i++) {
for (let j = i + 1; j < collisionShapes.length; j++) {
const shape1 = collisionShapes[i];
const shape2 = collisionShapes[j];
let collision = false;
let type = '';
if (shape1.type === 'rect' && shape2.type === 'rect') {
collision = rect_overlap(shape1.x, shape1.y, shape1.w, shape1.h,
shape2.x, shape2.y, shape2.w, shape2.h);
type = 'Rect-Rect';
} else if (shape1.type === 'circle' && shape2.type === 'circle') {
collision = circle_overlap(shape1.x, shape1.y, shape1.r,
shape2.x, shape2.y, shape2.r);
type = 'Circle-Circle';
} else if ((shape1.type === 'circle' && shape2.type === 'rect') ||
(shape1.type === 'rect' && shape2.type === 'circle')) {
const circle = shape1.type === 'circle' ? shape1 : shape2;
const rect = shape1.type === 'rect' ? shape1 : shape2;
collision = circle_rect_overlap(circle.x, circle.y, circle.r,
rect.x, rect.y, rect.w, rect.h);
type = 'Circle-Rect';
}
if (collision) {
collisionCount++;
log(`${type} collision detected between shape ${i+1} and ${j+1}`, 'collision-output');
}
}
}
if (collisionCount === 0 && collisionShapes.length > 1) {
log('No collisions detected', 'collision-output');
}
log(`Total shapes: ${collisionShapes.length}`, 'collision-output');
}
function clearCollisionCanvas() {
collisionShapes = [];
updateCollisionVisualization();
clearOutput('collision-output');
}
function testCollisions() {
clearOutput('collision-output');
const cx = parseFloat(document.getElementById('circle-x').value);
const cy = parseFloat(document.getElementById('circle-y').value);
const cr = parseFloat(document.getElementById('circle-r').value);
const rx = parseFloat(document.getElementById('rect-x').value);
const ry = parseFloat(document.getElementById('rect-y').value);
const rw = parseFloat(document.getElementById('rect-w').value);
const rh = parseFloat(document.getElementById('rect-h').value);
// Test all collision types
const circleRectOverlap = circle_rect_overlap(cx, cy, cr, rx, ry, rw, rh);
// Test point in shapes
const testX = cx, testY = cy; // Use circle center as test point
const pointInCircle = point_in_circle(testX, testY, cx, cy, cr);
const pointInRect = point_in_rect(testX, testY, rx, ry, rw, rh);
log(`Circle: (${cx}, ${cy}) r=${cr}`, 'collision-output');
log(`Rectangle: (${rx}, ${ry}) ${rw}x${rh}`, 'collision-output');
log(`Circle-Rect overlap: ${circleRectOverlap}`, 'collision-output');
log(`Point (${testX}, ${testY}) in circle: ${pointInCircle}`, 'collision-output');
log(`Point (${testX}, ${testY}) in rect: ${pointInRect}`, 'collision-output');
}
</script>
</body>
</html>