feat: add device motion interaction back

This commit is contained in:
Usu171
2025-12-14 21:52:05 +08:00
parent d9f361280d
commit b65d247d49
5 changed files with 178 additions and 6 deletions

View File

@@ -87,8 +87,13 @@ import Particles from "./Particles.astro";
// 4. Start Debug Info Loop
const updateDebug = () => {
const info = marbleSystem.getDeviceOrientationDebugInfo();
const info = marbleSystem.getAllDebugInfo();
debugEl.innerHTML = `
<div>MActive: ${info.motionActive}</div>
<div>MSupported: ${info.motionSupported}</div>
<div>MAX: ${info.motionAx}</div>
<div>MAY: ${info.motionAy}</div>
<div>MAForce: ${Math.hypot(parseFloat(info.motionAx), parseFloat(info.motionAy)).toFixed(2)}</div>
<div>Active: ${info.active}</div>
<div>Supported: ${info.supported}</div>
<div>AX: ${info.ax}</div>

View File

@@ -48,6 +48,10 @@ export const MARBLE_CONFIG = {
sensitivity: 600, // Sensitivity
maxForce: 6000, // Maximum force limit
},
deviceMotion: {
sensitivity: 600, // Sensitivity
maxForce: 6000, // Maximum force limit
},
} as const;
export const AVATAR_BASE_URL = "https://avatar.awfufu.com/qq/";

View File

@@ -0,0 +1,134 @@
/**
* Device motion interaction system
* Handles the effect of device tilt (acceleration) on marbles
*/
import type { Marble } from "./mouseInteraction";
export interface DeviceMotionConfig {
sensitivity: number;
maxForce: number;
}
export class DeviceMotionInteraction {
private ax: number = 0;
private ay: number = 0;
private isActive: boolean = false;
private config: DeviceMotionConfig;
constructor(config: DeviceMotionConfig) {
this.config = config;
}
/**
* Initialize device motion listener
*/
public init(): void {
if (typeof window === "undefined") return;
// Check if DeviceMotionEvent is supported
if (window.DeviceMotionEvent) {
window.addEventListener("devicemotion", this.handleMotion.bind(this));
this.isActive = true;
}
}
/**
* Handle device motion event
*/
private handleMotion(event: DeviceMotionEvent): void {
// x axis acceleration
// y axis acceleration
const accel = event.acceleration;
if (accel) {
this.ax = -(accel.x || 0);
this.ay = accel.y || 0;
}
}
/**
* Get Debug Info
*/
public getDebugInfo(): {
motionSupported: boolean;
motionActive: boolean;
motionAx: string;
motionAy: string;
} {
return {
motionActive: this.isActive,
motionSupported:
typeof window !== "undefined" && !!window.DeviceMotionEvent,
motionAx: this.ax.toFixed(2),
motionAy: this.ay.toFixed(2),
};
}
/**
* Get whether supported and active
*/
public isActivated(): boolean {
return this.isActive;
}
/**
* Request PermissioniOS 13+
*/
public async requestPermission(): Promise<boolean> {
if (typeof (DeviceMotionEvent as any).requestPermission === "function") {
try {
const response = await (DeviceMotionEvent as any).requestPermission();
if (response === "granted") {
this.init();
return true;
}
return false;
} catch (e) {
console.error("DeviceMotion permission error:", e);
return false;
}
}
return true; // Non iOS 13+ devices do not require a request
}
/**
* Apply Force
*/
public applyForce(marbles: Marble[], dt: number): void {
if (!this.isActive) return;
// Threshold filtering to prevent jitter
if (Math.abs(this.ax) < 0.5 && Math.abs(this.ay) < 0.5) return;
const { sensitivity, maxForce } = this.config;
// Calculate force
// ax, ay unit is m/s^2
// times sensitivity
let fx = this.ax * sensitivity;
let fy = this.ay * sensitivity;
// Limit max force
const force = Math.hypot(fx, fy);
if (force > maxForce) {
const scale = maxForce / force;
fx *= scale;
fy *= scale;
}
// Apply to all marbles
for (const m of marbles) {
// m.vx += fx;
// m.vy += fy;
m.vx += fx * dt;
m.vy += fy * dt;
// console.log("fx", fx, "fy", fy, "dt", dt);
}
}
/**
* Update Config
*/
public updateConfig(config: Partial<DeviceMotionConfig>): void {
this.config = { ...this.config, ...config };
}
}

View File

@@ -71,7 +71,8 @@ export class DeviceOrientationInteraction {
public getDebugInfo() {
return {
active: this.isActive,
supported: this.isSupported(),
supported:
typeof window !== "undefined" && !!window.DeviceOrientationEvent,
ax: this.ax.toFixed(2),
ay: this.ay.toFixed(2),
alpha: this.alpha?.toFixed(1),
@@ -83,7 +84,7 @@ export class DeviceOrientationInteraction {
/**
* Get whether supported and active
*/
public isSupported(): boolean {
public isActivated(): boolean {
return this.isActive;
}

View File

@@ -3,6 +3,7 @@
import type { UserEntry } from "../config/marbleConfig";
import { MARBLE_CONFIG } from "../config/marbleConfig";
import { DeviceOrientationInteraction } from "./deviceOrientationInteraction";
import { DeviceMotionInteraction } from "./deviceMotionInteraction";
import { AnimationLoop } from "./animationLoop";
import { MarbleFactory } from "./marbleFactory";
import { MarblePhysics } from "./marblePhysics";
@@ -23,6 +24,10 @@ export interface MarbleSystemConfig {
sensitivity?: number;
maxForce?: number;
};
deviceMotionConfig?: {
sensitivity?: number;
maxForce?: number;
};
}
export class MarbleSystem {
@@ -32,6 +37,7 @@ export class MarbleSystem {
// Subsystems
private mouseInteraction: MouseInteraction;
private deviceOrientationInteraction: DeviceOrientationInteraction;
private deviceMotionInteraction: DeviceMotionInteraction;
private physics: MarblePhysics;
private factory: MarbleFactory;
private animationLoop: AnimationLoop;
@@ -79,6 +85,18 @@ export class MarbleSystem {
});
this.deviceOrientationInteraction.init();
// DeviceMotionInteraction Init
this.deviceMotionInteraction = new DeviceMotionInteraction({
sensitivity:
config.deviceMotionConfig?.sensitivity ??
MARBLE_CONFIG.deviceMotion.sensitivity,
maxForce:
config.deviceMotionConfig?.maxForce ??
MARBLE_CONFIG.deviceMotion.maxForce,
});
this.deviceMotionInteraction.init();
// MarblePhysics Init
this.physics = new MarblePhysics({
fieldWidth: this.fieldWidth,
@@ -148,11 +166,16 @@ export class MarbleSystem {
}
}
// Apply device motion force
if (this.deviceOrientationInteraction.isSupported()) {
// Apply device orientation force
if (this.deviceOrientationInteraction.isActivated()) {
this.deviceOrientationInteraction.applyForce(this.marbles, subDt);
}
// Apply device motion force
if (this.deviceMotionInteraction.isActivated()) {
this.deviceMotionInteraction.applyForce(this.marbles, subDt);
}
// Update physics
this.physics.updatePositions(this.marbles, subDt);
this.physics.handleCollisions(this.marbles);
@@ -290,12 +313,17 @@ export class MarbleSystem {
return this.deviceOrientationInteraction.requestPermission();
}
public async requestDeviceMotionPermission(): Promise<boolean> {
return this.deviceMotionInteraction.requestPermission();
}
/**
* Get device motion debug info see also MainView.astro
*/
public getDeviceOrientationDebugInfo() {
public getAllDebugInfo() {
return {
...this.deviceOrientationInteraction.getDebugInfo(),
...this.deviceMotionInteraction.getDebugInfo(),
subSteps: this.currentSubSteps,
kineticEnergy: this.getKineticEnergy(),
minSpeed: this.physics.getConfig().minSpeed,