feat: add velocity vector display

This commit is contained in:
Usu171
2025-12-14 01:03:47 +08:00
parent 1efc80cfe3
commit 6437fd8825
4 changed files with 133 additions and 0 deletions

View File

@@ -12,6 +12,7 @@ import Particles from "./Particles.astro";
<div class="halo"></div>
<div class="grain"></div>
<div id="marble-field"></div>
<canvas id="debug-velocity-canvas"></canvas>
<Footer />
<div class="content">
@@ -315,6 +316,16 @@ import Particles from "./Particles.astro";
transition: opacity 0.5s ease;
}
#debug-velocity-canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
}
:global(.marble-wrapper) {
position: absolute;
will-change: transform, width, height;

View File

@@ -48,6 +48,12 @@ export const MARBLE_CONFIG = {
sensitivity: 600, // Sensitivity
maxForce: 6000, // Maximum force limit
},
// Debug configuration
debug: {
enabled: true, // Enable debug mode by default
vectorScale: 0.5, // Velocity vector scale factor
canvasId: "debug-velocity-canvas", // Debug canvas element ID
},
} as const;
export const AVATAR_BASE_URL = "https://avatar.awfufu.com/qq/";

View File

@@ -11,6 +11,8 @@ export interface PhysicsConfig {
minSpeed?: number; // Minimum speed
maxSpeed?: number; // Maximum speed
enableCollisions?: boolean; // Enable/Disable collisions
debugCanvas?: HTMLCanvasElement | null; // Optional canvas for debug rendering
debugVectorScale?: number; // Scale factor for velocity vectors (default: 0.5)
}
export class MarblePhysics {
@@ -26,6 +28,8 @@ export class MarblePhysics {
minSpeed: config.minSpeed ?? 30,
maxSpeed: config.maxSpeed ?? 800,
enableCollisions: config.enableCollisions ?? true,
debugCanvas: config.debugCanvas ?? null,
debugVectorScale: config.debugVectorScale ?? 0.5,
};
}
@@ -226,4 +230,60 @@ export class MarblePhysics {
public getConfig(): PhysicsConfig {
return { ...this.config };
}
// Render debug velocity vectors on canvas
public renderDebugVectors(marbles: Marble[]): void {
const canvas = this.config.debugCanvas;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
const scale = this.config.debugVectorScale ?? 0.5;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const m of marbles) {
const speed = Math.hypot(m.vx, m.vy);
if (speed < 1) continue; // Skip very slow marbles
// Calculate arrow end point
const endX = m.x + m.vx * scale;
const endY = m.y + m.vy * scale;
// Color based on speed (green -> yellow -> red)
const normalizedSpeed = Math.min(speed / 500, 1);
const hue = (1 - normalizedSpeed) * 120; // 120=green, 0=red
ctx.strokeStyle = `hsl(${hue}, 100%, 50%)`;
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
ctx.lineWidth = 2;
// Draw line
ctx.beginPath();
ctx.moveTo(m.x, m.y);
ctx.lineTo(endX, endY);
ctx.stroke();
// Draw arrowhead
const arrowSize = 8;
const angle = Math.atan2(m.vy, m.vx);
ctx.beginPath();
ctx.moveTo(endX, endY);
ctx.lineTo(
endX - arrowSize * Math.cos(angle - Math.PI / 6),
endY - arrowSize * Math.sin(angle - Math.PI / 6),
);
ctx.lineTo(
endX - arrowSize * Math.cos(angle + Math.PI / 6),
endY - arrowSize * Math.sin(angle + Math.PI / 6),
);
ctx.closePath();
ctx.fill();
// Draw speed text
ctx.font = "10px monospace";
ctx.fillText(`${speed.toFixed(0)}`, m.x + 5, m.y - 5);
}
}
}

View File

@@ -40,11 +40,26 @@ export class MarbleSystem {
private fieldWidth: number;
private fieldHeight: number;
// Debug mode
private debugMode: boolean = MARBLE_CONFIG.debug.enabled;
private debugCanvas: HTMLCanvasElement | null = null;
constructor(config: MarbleSystemConfig) {
this.container = config.container;
this.fieldWidth = config.fieldWidth;
this.fieldHeight = config.fieldHeight;
// Debug Canvas Init
if (MARBLE_CONFIG.debug.enabled) {
this.debugCanvas = document.getElementById(
MARBLE_CONFIG.debug.canvasId,
) as HTMLCanvasElement | null;
if (this.debugCanvas) {
this.debugCanvas.width = this.fieldWidth;
this.debugCanvas.height = this.fieldHeight;
}
}
// MouseInteraction Init
const mouseConfig: MouseInteractionConfig = {
attractRadius:
@@ -83,6 +98,8 @@ export class MarbleSystem {
wallBounce: MARBLE_CONFIG.physics.wallBounce,
minSpeed: MARBLE_CONFIG.physics.minSpeed,
maxSpeed: MARBLE_CONFIG.physics.maxSpeed,
debugCanvas: this.debugCanvas,
debugVectorScale: MARBLE_CONFIG.debug.vectorScale,
});
// MarbleFactory Init
@@ -122,6 +139,11 @@ export class MarbleSystem {
this.physics.handleCollisions(this.marbles);
this.physics.handleBoundaries(this.marbles);
this.physics.render(this.marbles);
// Render debug vectors if enabled
if (this.debugMode) {
this.physics.renderDebugVectors(this.marbles);
}
}
// Set up window resize listener
@@ -249,4 +271,38 @@ export class MarbleSystem {
public setCollisions(enabled: boolean): void {
this.physics.updateConfig({ enableCollisions: enabled });
}
// Toggle debug mode (show velocity vectors)
public setDebugMode(enabled: boolean): void {
this.debugMode = enabled;
// Get canvas from DOM if not already cached
if (!this.debugCanvas) {
this.debugCanvas = document.getElementById(
MARBLE_CONFIG.debug.canvasId,
) as HTMLCanvasElement | null;
}
if (this.debugCanvas) {
if (enabled) {
// Show canvas and configure physics
this.debugCanvas.style.display = "block";
this.debugCanvas.width = this.fieldWidth;
this.debugCanvas.height = this.fieldHeight;
this.physics.updateConfig({ debugCanvas: this.debugCanvas });
} else {
// Hide canvas and clear
this.debugCanvas.style.display = "none";
const ctx = this.debugCanvas.getContext("2d");
if (ctx)
ctx.clearRect(0, 0, this.debugCanvas.width, this.debugCanvas.height);
this.physics.updateConfig({ debugCanvas: null });
}
}
}
// Get debug mode status
public isDebugMode(): boolean {
return this.debugMode;
}
}