mirror of
https://github.com/101island/lolisland.us.git
synced 2026-03-01 03:49:42 +08:00
feat: add device motion interaction back
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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/";
|
||||
|
||||
134
src/utils/deviceMotionInteraction.ts
Normal file
134
src/utils/deviceMotionInteraction.ts
Normal 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 Permission(iOS 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 };
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user