Prompt utilisé pour régénérer cette page :
Page: Raycaster
Description: "Wolfenstein-style pseudo-3D rendering engine with DDA raycasting"
Category: canvas
Icon: cube
Tags: rendering, 3d, raycasting
Status: new
Front matter (index.md):
title: "Raycaster"
description: "Wolfenstein-style pseudo-3D rendering engine with DDA raycasting"
icon: "cube"
tags: ["rendering", "3d", "raycasting"]
status: ["new"]
HTML structure (index.md):
<section class="container visual size-800 ratio-16-9 canvas-contain">
<canvas id="raycaster-canvas"></canvas>
</section>
NOTE: This page uses ratio-16-9 (not 1-1 like most other pages).
Widget files:
- _minimap.right.md (weight: 10): div.raycaster-minimap with:
<canvas id="minimap-canvas" width="200" height="200">
- _controls.right.md (weight: 20): Contains keyboard reference, settings, and stats all in one widget:
div.raycaster-controls:
##### Keyboard — dl.raycaster-keys (grid auto 1fr):
W/↑: Move forward, S/↓: Move backward, A/←: Rotate left, D/→: Rotate right
Uses <kbd> tags for keys
div.raycaster-options:
fieldset with legend "Settings":
FOV: slider-fov (30-120, step 5, value 60), display span#display-fov with ° suffix
div.raycaster-stats:
##### Stats — <dl> with FPS, Position, Angle
Stat IDs: stat-fps, stat-position, stat-angle
- _algorithm.after.md (weight: 90): Explains DDA raycasting algorithm: per-column ray casting, grid stepping via side distance comparison, perpendicular distance to avoid fisheye, wall height formula (screenHeight/perpDist), face-based shading.
Architecture (single file default.js):
- IIFE, imports panic from /_lib/panic_v3.js
- No external dependencies
SCSS file (default.scss):
- CSS custom properties: --raycaster-color-ceiling (#333), --raycaster-color-floor (#666), --raycaster-color-wall-1 through --raycaster-color-wall-4 mapped to --color-red/blue/green/yellow
- #raycaster-canvas: outline:none, &:focus border-color primary
- .raycaster-minimap: flex centered, canvas 100% width max-width 200px, 1:1 aspect ratio, border+radius
- .raycaster-controls: dl.raycaster-keys grid (auto 1fr), kbd styling (monospace, small, bordered, bg surface)
- .raycaster-options: flex column with fieldset/legend, .option with label+range, span monospace+primary
- .raycaster-stats: dl grid (1fr auto), dt secondary 0.875rem, dd monospace right-aligned
CONFIG defaults:
canvasWidth: 800, canvasHeight: 450 (16:9 aspect ratio),
startX: 2.5, startY: 2.5, startAngle: 0,
moveSpeed: 0.05, rotSpeed: 0.03, fov: 60,
minimapSize: 200
Map (MAP constant):
- 16×16 2D array, values 0-4
- 0 = empty space, 1-4 = wall types with distinct colors
- Outer ring: all type 1 walls
- Inner structures form rooms: type 2 (blue) top-left+bottom-right, type 3 (green) top-right+bottom-left, type 4 (yellow) center room
- MAP_SIZE = MAP.length (16)
Wall colors (wallColors object, RGB arrays per type):
1: [180, 60, 60] (red), 2: [70, 130, 200] (blue),
3: [60, 180, 80] (green), 4: [220, 200, 60] (yellow)
- Optionally overridden from CSS vars via cacheColors() + parseRGB()
Player state:
- playerX, playerY: float world coordinates (start at 2.5, 2.5)
- playerAngle: radians (start at 0)
- fov: degrees (default 60), converted to radians for rendering
- keys: Set of currently pressed key names
Keyboard input (processInput):
- ArrowLeft/a: rotate left by rotSpeed (0.03)
- ArrowRight/d: rotate right by rotSpeed
- Angle normalized via modulo 2*PI to prevent float drift
- ArrowUp/w: move forward — dx = cos(angle)*moveSpeed, dy = sin(angle)*moveSpeed
- ArrowDown/s: move backward (subtract dx/dy)
- Per-axis collision detection: check isWalkable(newX, playerY) and isWalkable(playerX, newY) independently
- Allows wall sliding behavior
- isWalkable(x, y): bounds check + MAP[floor(y)][floor(x)] === 0
Key handling:
- Keys tracked on canvas element (requires focus) via keydown/keyup
- Both e.key and e.key.toLowerCase() stored for arrow key cross-reference
- Arrow keys: preventDefault to prevent page scrolling
- blur event: keys.clear() to prevent stuck keys
- Canvas click: focus canvas and auto-start if not running
DDA raycasting (castRay):
- Input: ray angle in radians
- Ray direction: rayDirX = cos(angle), rayDirY = sin(angle)
- Delta distances: deltaDistX = |1/rayDirX|, deltaDistY = |1/rayDirY|
- Step direction (±1) and initial side distances computed from player fractional position
- DDA loop: max 64 steps
- Advance along whichever axis has shortest side distance
- sideDistX < sideDistY → step X, side=0; else step Y, side=1
- Bounds check after each step
- Hit when MAP[mapY][mapX] > 0
- Perpendicular distance (eliminates fisheye):
- side 0: perpDist = sideDistX - deltaDistX
- side 1: perpDist = sideDistY - deltaDistY
- Clamped to min 0.001
- Returns: {dist, wallType, side, hitX, hitY}
3D rendering (render3D):
- Ceiling: linear gradient (#111 at top → #333 at horizon)
- Floor: linear gradient (#444 at horizon → #666 at bottom)
- For each screen column (0 to width):
- Ray offset = (col/width - 0.5) * fovRad
- Cast ray at playerAngle + offset
- Wall height = canvasHeight / perpDist
- Draw vertical strip: y from max(0, halfH - height/2) to min(h, halfH + height/2)
- Color computation:
- Base color: wallColors[hit.wallType] or wallColors[1] as fallback
- Distance shading: shade = min(1, 1.5/(dist + 0.5))
- Side dimming: sideFactor = 0.7 for Y-faces (side=1), 1.0 for X-faces
- Final RGB = floor(baseColor * shade * sideFactor) per channel
- Drawn as 1px wide fillRect
Minimap rendering (renderMinimap):
- Drawn on separate canvas (id="minimap-canvas"), fixed 200×200 pixels, no DPI scaling
- Clear with cachedColors.minimapBg (#111)
- Wall cells: colored rectangles using wallColors RGB
- Grid lines: white at alpha 0.1, line width 0.5
- 20 sample rays: from player position to hit points, cachedColors.minimapRay (rgba yellow, alpha 0.15)
- Player dot: cachedColors.minimapPlayer (red), radius 3, filled circle
- Direction indicator: red line from player in playerAngle direction, length 1.5× cellSize
Statistics (updateStats):
- FPS: stat-fps element
- Position: stat-position element, playerX.toFixed(1) + ', ' + playerY.toFixed(1)
- Angle: stat-angle element, degrees with ° suffix (unicode \u00B0)
Animation loop:
- animate(timestamp) via requestAnimationFrame
- FPS counter: frameCount per second
- Each frame: processInput → render3D → renderMinimap → updateStats
- Auto-starts on init via start() call
Color caching:
- Wall colors optionally overridden from CSS vars (raycaster-color-wall-1 through 4)
- parseRGB(color): handles #rgb (short hex), #rrggbb (long hex), and rgb() CSS formats; fallback [128,128,128]
- Refreshed on prefers-color-scheme change
Canvas setup:
- Main canvas: 800×450 × devicePixelRatio (16:9), ctx.scale(dpr, dpr)
- Minimap canvas: fixed 200×200 pixels (no DPI scaling)
- Main canvas made focusable via canvas.tabIndex = 0
Auto-starts animation on initialization. Click canvas to gain keyboard focus.
Auto-init on DOM ready (DOMContentLoaded or immediate if already loaded).
Page entièrement générée et maintenue par IA, sans intervention humaine.