@hapticjs/gamepad 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +36 -0
- package/dist/index.cjs +113 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.js +113 -1
- package/dist/index.js.map +1 -1
- package/package.json +9 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thirumalesh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# @hapticjs/gamepad
|
|
2
|
+
|
|
3
|
+
Gamepad haptics adapter for the @hapticjs haptic engine. Supports dual-motor rumble on controllers.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @hapticjs/gamepad @hapticjs/core
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Usage
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { HapticEngine } from '@hapticjs/core';
|
|
13
|
+
import { GamepadHapticAdapter, GamepadManager } from '@hapticjs/gamepad';
|
|
14
|
+
|
|
15
|
+
const adapter = new GamepadHapticAdapter({ gamepadIndex: 0 });
|
|
16
|
+
const engine = HapticEngine.create({ adapter });
|
|
17
|
+
|
|
18
|
+
// Play haptic patterns on the controller
|
|
19
|
+
engine.play('@@..@@..@@');
|
|
20
|
+
engine.impact('heavy');
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Motor Mapping
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
import { defaultMotorMapping, heavyMotorMapping, equalMotorMapping } from '@hapticjs/gamepad';
|
|
27
|
+
|
|
28
|
+
const adapter = new GamepadHapticAdapter({
|
|
29
|
+
gamepadIndex: 0,
|
|
30
|
+
motorMapping: heavyMotorMapping, // Emphasize the heavy motor
|
|
31
|
+
});
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## License
|
|
35
|
+
|
|
36
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -155,8 +155,121 @@ var GamepadHapticAdapter = class {
|
|
|
155
155
|
}
|
|
156
156
|
};
|
|
157
157
|
|
|
158
|
+
// src/spatial/spatial-haptics.ts
|
|
159
|
+
var SpatialHaptics = class {
|
|
160
|
+
constructor(adapter) {
|
|
161
|
+
this.adapter = adapter;
|
|
162
|
+
}
|
|
163
|
+
// ─── Directional ─────────────────────────────────────────
|
|
164
|
+
/** Vibrate left motor only (weak/high-frequency motor) */
|
|
165
|
+
async left(intensity = 0.7, duration = 50) {
|
|
166
|
+
duration = Math.max(25, duration);
|
|
167
|
+
await this._playDualRumble(intensity, 0, duration);
|
|
168
|
+
}
|
|
169
|
+
/** Vibrate right motor only (strong/low-frequency motor) */
|
|
170
|
+
async right(intensity = 0.7, duration = 50) {
|
|
171
|
+
duration = Math.max(25, duration);
|
|
172
|
+
await this._playDualRumble(0, intensity, duration);
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Sweep vibration from one side to the other.
|
|
176
|
+
* Plays first side then second side with slight overlap.
|
|
177
|
+
*/
|
|
178
|
+
async sweep(direction, duration = 200) {
|
|
179
|
+
duration = Math.max(50, duration);
|
|
180
|
+
const halfDuration = Math.max(25, Math.floor(duration / 2));
|
|
181
|
+
if (direction === "left-to-right") {
|
|
182
|
+
await this._playDualRumble(0.8, 0, halfDuration);
|
|
183
|
+
await this._delay(Math.max(25, halfDuration - 25));
|
|
184
|
+
await this._playDualRumble(0, 0.8, halfDuration);
|
|
185
|
+
} else {
|
|
186
|
+
await this._playDualRumble(0, 0.8, halfDuration);
|
|
187
|
+
await this._delay(Math.max(25, halfDuration - 25));
|
|
188
|
+
await this._playDualRumble(0.8, 0, halfDuration);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/** Pulsing haptic on specified side */
|
|
192
|
+
async pulse(side = "both", count = 3, interval = 100) {
|
|
193
|
+
interval = Math.max(50, interval);
|
|
194
|
+
const pulseDuration = Math.max(25, Math.floor(interval * 0.4));
|
|
195
|
+
for (let i = 0; i < count; i++) {
|
|
196
|
+
if (side === "left") {
|
|
197
|
+
await this._playDualRumble(0.7, 0, pulseDuration);
|
|
198
|
+
} else if (side === "right") {
|
|
199
|
+
await this._playDualRumble(0, 0.7, pulseDuration);
|
|
200
|
+
} else {
|
|
201
|
+
await this._playDualRumble(0.7, 0.7, pulseDuration);
|
|
202
|
+
}
|
|
203
|
+
if (i < count - 1) {
|
|
204
|
+
await this._delay(interval - pulseDuration);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// ─── Rumble ──────────────────────────────────────────────
|
|
209
|
+
/** Sustained left rumble */
|
|
210
|
+
async rumbleLeft(duration = 300, intensity = 0.6) {
|
|
211
|
+
duration = Math.max(25, duration);
|
|
212
|
+
await this._playDualRumble(intensity, 0, duration);
|
|
213
|
+
}
|
|
214
|
+
/** Sustained right rumble */
|
|
215
|
+
async rumbleRight(duration = 300, intensity = 0.6) {
|
|
216
|
+
duration = Math.max(25, duration);
|
|
217
|
+
await this._playDualRumble(0, intensity, duration);
|
|
218
|
+
}
|
|
219
|
+
// ─── Impact ──────────────────────────────────────────────
|
|
220
|
+
/**
|
|
221
|
+
* Directional impact feedback.
|
|
222
|
+
* Center hits both motors simultaneously.
|
|
223
|
+
*/
|
|
224
|
+
async impact(direction, force = 0.9) {
|
|
225
|
+
const impactDuration = 50;
|
|
226
|
+
if (direction === "left") {
|
|
227
|
+
await this._playDualRumble(force, force * 0.2, impactDuration);
|
|
228
|
+
} else if (direction === "right") {
|
|
229
|
+
await this._playDualRumble(force * 0.2, force, impactDuration);
|
|
230
|
+
} else {
|
|
231
|
+
await this._playDualRumble(force, force, impactDuration);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// ─── Simulation ──────────────────────────────────────────
|
|
235
|
+
/**
|
|
236
|
+
* Engine RPM simulation.
|
|
237
|
+
* Low RPM = slow weak pulses, high RPM = fast strong pulses.
|
|
238
|
+
* RPM range: 0-8000.
|
|
239
|
+
*/
|
|
240
|
+
async engine(rpm) {
|
|
241
|
+
const normalizedRPM = Math.min(8e3, Math.max(0, rpm));
|
|
242
|
+
const rpmFactor = normalizedRPM / 8e3;
|
|
243
|
+
const weakMag = Math.min(1, 0.2 + rpmFactor * 0.5);
|
|
244
|
+
const strongMag = Math.min(1, rpmFactor * 0.9);
|
|
245
|
+
const pulseDuration = Math.max(25, Math.floor(150 - rpmFactor * 120));
|
|
246
|
+
await this._playDualRumble(weakMag, strongMag, pulseDuration);
|
|
247
|
+
}
|
|
248
|
+
// ─── Internal ────────────────────────────────────────────
|
|
249
|
+
/**
|
|
250
|
+
* Play a dual-rumble effect with explicit weak/strong magnitudes.
|
|
251
|
+
* Uses the adapter's pulse method internally, but crafts the sequence
|
|
252
|
+
* to target specific motors via the gamepad vibrationActuator API.
|
|
253
|
+
*/
|
|
254
|
+
async _playDualRumble(weakMagnitude, strongMagnitude, duration) {
|
|
255
|
+
const manager = this.adapter.getManager();
|
|
256
|
+
const gamepad = manager.getFirstGamepad();
|
|
257
|
+
if (!gamepad?.vibrationActuator) return;
|
|
258
|
+
await gamepad.vibrationActuator.playEffect("dual-rumble", {
|
|
259
|
+
startDelay: 0,
|
|
260
|
+
duration: Math.max(25, duration),
|
|
261
|
+
weakMagnitude: Math.min(1, Math.max(0, weakMagnitude)),
|
|
262
|
+
strongMagnitude: Math.min(1, Math.max(0, strongMagnitude))
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
_delay(ms) {
|
|
266
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
|
|
158
270
|
exports.GamepadHapticAdapter = GamepadHapticAdapter;
|
|
159
271
|
exports.GamepadManager = GamepadManager;
|
|
272
|
+
exports.SpatialHaptics = SpatialHaptics;
|
|
160
273
|
exports.defaultMotorMapping = defaultMotorMapping;
|
|
161
274
|
exports.equalMotorMapping = equalMotorMapping;
|
|
162
275
|
exports.heavyMotorMapping = heavyMotorMapping;
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/gamepad-manager.ts","../src/utils/dual-motor.ts","../src/adapters/gamepad-haptic.adapter.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/** Manages gamepad connection state and provides access to connected gamepads */\nexport class GamepadManager {\n private _onConnect?: (gamepad: Gamepad) => void;\n private _onDisconnect?: (gamepad: Gamepad) => void;\n private _listening = false;\n\n /** Start listening for gamepad connections */\n listen(): void {\n if (this._listening || typeof window === 'undefined') return;\n this._listening = true;\n\n window.addEventListener('gamepadconnected', this._handleConnect);\n window.addEventListener('gamepaddisconnected', this._handleDisconnect);\n }\n\n /** Stop listening */\n unlisten(): void {\n if (!this._listening || typeof window === 'undefined') return;\n this._listening = false;\n\n window.removeEventListener('gamepadconnected', this._handleConnect);\n window.removeEventListener('gamepaddisconnected', this._handleDisconnect);\n }\n\n /** Get all currently connected gamepads */\n getGamepads(): Gamepad[] {\n if (typeof navigator === 'undefined' || !('getGamepads' in navigator)) {\n return [];\n }\n return Array.from(navigator.getGamepads()).filter(\n (gp): gp is Gamepad => gp !== null\n );\n }\n\n /** Get the first connected gamepad, or null */\n getFirstGamepad(): Gamepad | null {\n const gamepads = this.getGamepads();\n return gamepads[0] ?? null;\n }\n\n /** Set connection callback */\n onConnect(fn: (gamepad: Gamepad) => void): void {\n this._onConnect = fn;\n }\n\n /** Set disconnection callback */\n onDisconnect(fn: (gamepad: Gamepad) => void): void {\n this._onDisconnect = fn;\n }\n\n /** Check if any gamepad with haptic support is connected */\n hasHapticGamepad(): boolean {\n return this.getGamepads().some(\n (gp) => gp.vibrationActuator != null\n );\n }\n\n dispose(): void {\n this.unlisten();\n this._onConnect = undefined;\n this._onDisconnect = undefined;\n }\n\n private _handleConnect = (e: GamepadEvent) => {\n this._onConnect?.(e.gamepad);\n };\n\n private _handleDisconnect = (e: GamepadEvent) => {\n this._onDisconnect?.(e.gamepad);\n };\n}\n","/** Dual motor parameters for gamepad haptics */\nexport interface DualMotorParams {\n /** Weak (high-frequency) motor magnitude 0-1 */\n weakMagnitude: number;\n /** Strong (low-frequency) motor magnitude 0-1 */\n strongMagnitude: number;\n}\n\nexport type MotorMappingFn = (intensity: number) => DualMotorParams;\n\n/**\n * Default motor mapping: light effects use weak motor, heavy use strong, medium uses both.\n */\nexport const defaultMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: Math.min(intensity * 1.5, 1.0),\n strongMagnitude: Math.max(intensity - 0.3, 0),\n };\n};\n\n/**\n * Equal mapping: both motors at same intensity.\n */\nexport const equalMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: intensity,\n strongMagnitude: intensity,\n };\n};\n\n/**\n * Heavy mapping: emphasizes the strong (low-frequency) motor.\n */\nexport const heavyMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: intensity * 0.3,\n strongMagnitude: intensity,\n };\n};\n","import type { HapticAdapter, AdapterCapabilities, HapticStep } from '@hapticjs/core';\nimport { GamepadManager } from '../utils/gamepad-manager';\nimport { defaultMotorMapping } from '../utils/dual-motor';\nimport type { MotorMappingFn } from '../utils/dual-motor';\n\nexport interface GamepadAdapterOptions {\n /** Which gamepad index to use (default: first connected) */\n gamepadIndex?: number;\n /** Custom motor mapping function */\n motorMapping?: MotorMappingFn;\n /** Auto-listen for gamepad connections (default: true) */\n autoListen?: boolean;\n}\n\n/**\n * Gamepad haptics adapter using the GamepadHapticActuator API.\n * Supports dual-motor (weak/strong) vibration on modern controllers.\n */\nexport class GamepadHapticAdapter implements HapticAdapter {\n readonly name = 'gamepad';\n private manager: GamepadManager;\n private motorMapping: MotorMappingFn;\n private gamepadIndex?: number;\n private _cancelled = false;\n\n constructor(options: GamepadAdapterOptions = {}) {\n this.manager = new GamepadManager();\n this.motorMapping = options.motorMapping ?? defaultMotorMapping;\n this.gamepadIndex = options.gamepadIndex;\n\n if (options.autoListen !== false) {\n this.manager.listen();\n }\n }\n\n get supported(): boolean {\n return this.manager.hasHapticGamepad();\n }\n\n capabilities(): AdapterCapabilities {\n return {\n maxIntensityLevels: 100,\n minDuration: 1,\n maxDuration: 5000,\n supportsPattern: false,\n supportsIntensity: true,\n dualMotor: true,\n };\n }\n\n async pulse(intensity: number, duration: number): Promise<void> {\n const gamepad = this._getGamepad();\n if (!gamepad?.vibrationActuator) return;\n\n const { weakMagnitude, strongMagnitude } = this.motorMapping(intensity);\n\n await gamepad.vibrationActuator.playEffect('dual-rumble', {\n startDelay: 0,\n duration,\n weakMagnitude,\n strongMagnitude,\n });\n }\n\n async playSequence(steps: HapticStep[]): Promise<void> {\n this._cancelled = false;\n\n for (const step of steps) {\n if (this._cancelled) break;\n\n if (step.type === 'vibrate' && step.intensity > 0) {\n await this.pulse(step.intensity, step.duration);\n await this._delay(step.duration);\n } else {\n await this._delay(step.duration);\n }\n }\n }\n\n cancel(): void {\n this._cancelled = true;\n const gamepad = this._getGamepad();\n if (gamepad?.vibrationActuator) {\n gamepad.vibrationActuator.reset();\n }\n }\n\n dispose(): void {\n this.cancel();\n this.manager.dispose();\n }\n\n /** Get the gamepad manager for advanced usage */\n getManager(): GamepadManager {\n return this.manager;\n }\n\n private _getGamepad(): Gamepad | null {\n if (this.gamepadIndex !== undefined) {\n const gamepads = this.manager.getGamepads();\n return gamepads.find((gp) => gp.index === this.gamepadIndex) ?? null;\n }\n return this.manager.getFirstGamepad();\n }\n\n private _delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/gamepad-manager.ts","../src/utils/dual-motor.ts","../src/adapters/gamepad-haptic.adapter.ts","../src/spatial/spatial-haptics.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/** Manages gamepad connection state and provides access to connected gamepads */\nexport class GamepadManager {\n private _onConnect?: (gamepad: Gamepad) => void;\n private _onDisconnect?: (gamepad: Gamepad) => void;\n private _listening = false;\n\n /** Start listening for gamepad connections */\n listen(): void {\n if (this._listening || typeof window === 'undefined') return;\n this._listening = true;\n\n window.addEventListener('gamepadconnected', this._handleConnect);\n window.addEventListener('gamepaddisconnected', this._handleDisconnect);\n }\n\n /** Stop listening */\n unlisten(): void {\n if (!this._listening || typeof window === 'undefined') return;\n this._listening = false;\n\n window.removeEventListener('gamepadconnected', this._handleConnect);\n window.removeEventListener('gamepaddisconnected', this._handleDisconnect);\n }\n\n /** Get all currently connected gamepads */\n getGamepads(): Gamepad[] {\n if (typeof navigator === 'undefined' || !('getGamepads' in navigator)) {\n return [];\n }\n return Array.from(navigator.getGamepads()).filter(\n (gp): gp is Gamepad => gp !== null\n );\n }\n\n /** Get the first connected gamepad, or null */\n getFirstGamepad(): Gamepad | null {\n const gamepads = this.getGamepads();\n return gamepads[0] ?? null;\n }\n\n /** Set connection callback */\n onConnect(fn: (gamepad: Gamepad) => void): void {\n this._onConnect = fn;\n }\n\n /** Set disconnection callback */\n onDisconnect(fn: (gamepad: Gamepad) => void): void {\n this._onDisconnect = fn;\n }\n\n /** Check if any gamepad with haptic support is connected */\n hasHapticGamepad(): boolean {\n return this.getGamepads().some(\n (gp) => gp.vibrationActuator != null\n );\n }\n\n dispose(): void {\n this.unlisten();\n this._onConnect = undefined;\n this._onDisconnect = undefined;\n }\n\n private _handleConnect = (e: GamepadEvent) => {\n this._onConnect?.(e.gamepad);\n };\n\n private _handleDisconnect = (e: GamepadEvent) => {\n this._onDisconnect?.(e.gamepad);\n };\n}\n","/** Dual motor parameters for gamepad haptics */\nexport interface DualMotorParams {\n /** Weak (high-frequency) motor magnitude 0-1 */\n weakMagnitude: number;\n /** Strong (low-frequency) motor magnitude 0-1 */\n strongMagnitude: number;\n}\n\nexport type MotorMappingFn = (intensity: number) => DualMotorParams;\n\n/**\n * Default motor mapping: light effects use weak motor, heavy use strong, medium uses both.\n */\nexport const defaultMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: Math.min(intensity * 1.5, 1.0),\n strongMagnitude: Math.max(intensity - 0.3, 0),\n };\n};\n\n/**\n * Equal mapping: both motors at same intensity.\n */\nexport const equalMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: intensity,\n strongMagnitude: intensity,\n };\n};\n\n/**\n * Heavy mapping: emphasizes the strong (low-frequency) motor.\n */\nexport const heavyMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: intensity * 0.3,\n strongMagnitude: intensity,\n };\n};\n","import type { HapticAdapter, AdapterCapabilities, HapticStep } from '@hapticjs/core';\nimport { GamepadManager } from '../utils/gamepad-manager';\nimport { defaultMotorMapping } from '../utils/dual-motor';\nimport type { MotorMappingFn } from '../utils/dual-motor';\n\nexport interface GamepadAdapterOptions {\n /** Which gamepad index to use (default: first connected) */\n gamepadIndex?: number;\n /** Custom motor mapping function */\n motorMapping?: MotorMappingFn;\n /** Auto-listen for gamepad connections (default: true) */\n autoListen?: boolean;\n}\n\n/**\n * Gamepad haptics adapter using the GamepadHapticActuator API.\n * Supports dual-motor (weak/strong) vibration on modern controllers.\n */\nexport class GamepadHapticAdapter implements HapticAdapter {\n readonly name = 'gamepad';\n private manager: GamepadManager;\n private motorMapping: MotorMappingFn;\n private gamepadIndex?: number;\n private _cancelled = false;\n\n constructor(options: GamepadAdapterOptions = {}) {\n this.manager = new GamepadManager();\n this.motorMapping = options.motorMapping ?? defaultMotorMapping;\n this.gamepadIndex = options.gamepadIndex;\n\n if (options.autoListen !== false) {\n this.manager.listen();\n }\n }\n\n get supported(): boolean {\n return this.manager.hasHapticGamepad();\n }\n\n capabilities(): AdapterCapabilities {\n return {\n maxIntensityLevels: 100,\n minDuration: 1,\n maxDuration: 5000,\n supportsPattern: false,\n supportsIntensity: true,\n dualMotor: true,\n };\n }\n\n async pulse(intensity: number, duration: number): Promise<void> {\n const gamepad = this._getGamepad();\n if (!gamepad?.vibrationActuator) return;\n\n const { weakMagnitude, strongMagnitude } = this.motorMapping(intensity);\n\n await gamepad.vibrationActuator.playEffect('dual-rumble', {\n startDelay: 0,\n duration,\n weakMagnitude,\n strongMagnitude,\n });\n }\n\n async playSequence(steps: HapticStep[]): Promise<void> {\n this._cancelled = false;\n\n for (const step of steps) {\n if (this._cancelled) break;\n\n if (step.type === 'vibrate' && step.intensity > 0) {\n await this.pulse(step.intensity, step.duration);\n await this._delay(step.duration);\n } else {\n await this._delay(step.duration);\n }\n }\n }\n\n cancel(): void {\n this._cancelled = true;\n const gamepad = this._getGamepad();\n if (gamepad?.vibrationActuator) {\n gamepad.vibrationActuator.reset();\n }\n }\n\n dispose(): void {\n this.cancel();\n this.manager.dispose();\n }\n\n /** Get the gamepad manager for advanced usage */\n getManager(): GamepadManager {\n return this.manager;\n }\n\n private _getGamepad(): Gamepad | null {\n if (this.gamepadIndex !== undefined) {\n const gamepads = this.manager.getGamepads();\n return gamepads.find((gp) => gp.index === this.gamepadIndex) ?? null;\n }\n return this.manager.getFirstGamepad();\n }\n\n private _delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { GamepadHapticAdapter } from '../adapters/gamepad-haptic.adapter';\n\n/**\n * SpatialHaptics — directional haptic feedback for dual-motor controllers.\n *\n * Provides left/right motor control, sweeps, pulses, impacts,\n * and engine RPM simulation for immersive spatial feedback.\n */\nexport class SpatialHaptics {\n private adapter: GamepadHapticAdapter;\n\n constructor(adapter: GamepadHapticAdapter) {\n this.adapter = adapter;\n }\n\n // ─── Directional ─────────────────────────────────────────\n\n /** Vibrate left motor only (weak/high-frequency motor) */\n async left(intensity = 0.7, duration = 50): Promise<void> {\n duration = Math.max(25, duration);\n await this._playDualRumble(intensity, 0, duration);\n }\n\n /** Vibrate right motor only (strong/low-frequency motor) */\n async right(intensity = 0.7, duration = 50): Promise<void> {\n duration = Math.max(25, duration);\n await this._playDualRumble(0, intensity, duration);\n }\n\n /**\n * Sweep vibration from one side to the other.\n * Plays first side then second side with slight overlap.\n */\n async sweep(\n direction: 'left-to-right' | 'right-to-left',\n duration = 200,\n ): Promise<void> {\n duration = Math.max(50, duration);\n const halfDuration = Math.max(25, Math.floor(duration / 2));\n\n if (direction === 'left-to-right') {\n await this._playDualRumble(0.8, 0, halfDuration);\n await this._delay(Math.max(25, halfDuration - 25));\n await this._playDualRumble(0, 0.8, halfDuration);\n } else {\n await this._playDualRumble(0, 0.8, halfDuration);\n await this._delay(Math.max(25, halfDuration - 25));\n await this._playDualRumble(0.8, 0, halfDuration);\n }\n }\n\n /** Pulsing haptic on specified side */\n async pulse(\n side: 'left' | 'right' | 'both' = 'both',\n count = 3,\n interval = 100,\n ): Promise<void> {\n interval = Math.max(50, interval);\n const pulseDuration = Math.max(25, Math.floor(interval * 0.4));\n\n for (let i = 0; i < count; i++) {\n if (side === 'left') {\n await this._playDualRumble(0.7, 0, pulseDuration);\n } else if (side === 'right') {\n await this._playDualRumble(0, 0.7, pulseDuration);\n } else {\n await this._playDualRumble(0.7, 0.7, pulseDuration);\n }\n\n if (i < count - 1) {\n await this._delay(interval - pulseDuration);\n }\n }\n }\n\n // ─── Rumble ──────────────────────────────────────────────\n\n /** Sustained left rumble */\n async rumbleLeft(duration = 300, intensity = 0.6): Promise<void> {\n duration = Math.max(25, duration);\n await this._playDualRumble(intensity, 0, duration);\n }\n\n /** Sustained right rumble */\n async rumbleRight(duration = 300, intensity = 0.6): Promise<void> {\n duration = Math.max(25, duration);\n await this._playDualRumble(0, intensity, duration);\n }\n\n // ─── Impact ──────────────────────────────────────────────\n\n /**\n * Directional impact feedback.\n * Center hits both motors simultaneously.\n */\n async impact(\n direction: 'left' | 'right' | 'center',\n force = 0.9,\n ): Promise<void> {\n const impactDuration = 50;\n\n if (direction === 'left') {\n await this._playDualRumble(force, force * 0.2, impactDuration);\n } else if (direction === 'right') {\n await this._playDualRumble(force * 0.2, force, impactDuration);\n } else {\n await this._playDualRumble(force, force, impactDuration);\n }\n }\n\n // ─── Simulation ──────────────────────────────────────────\n\n /**\n * Engine RPM simulation.\n * Low RPM = slow weak pulses, high RPM = fast strong pulses.\n * RPM range: 0-8000.\n */\n async engine(rpm: number): Promise<void> {\n const normalizedRPM = Math.min(8000, Math.max(0, rpm));\n const rpmFactor = normalizedRPM / 8000;\n\n // Map RPM to motor intensities\n const weakMag = Math.min(1, 0.2 + rpmFactor * 0.5);\n const strongMag = Math.min(1, rpmFactor * 0.9);\n\n // Map RPM to pulse duration (shorter at high RPM)\n const pulseDuration = Math.max(25, Math.floor(150 - rpmFactor * 120));\n\n await this._playDualRumble(weakMag, strongMag, pulseDuration);\n }\n\n // ─── Internal ────────────────────────────────────────────\n\n /**\n * Play a dual-rumble effect with explicit weak/strong magnitudes.\n * Uses the adapter's pulse method internally, but crafts the sequence\n * to target specific motors via the gamepad vibrationActuator API.\n */\n private async _playDualRumble(\n weakMagnitude: number,\n strongMagnitude: number,\n duration: number,\n ): Promise<void> {\n // Use the adapter's playSequence with a custom motor mapping\n // by directly accessing the adapter's internal gamepad\n const manager = this.adapter.getManager();\n const gamepad = manager.getFirstGamepad();\n if (!gamepad?.vibrationActuator) return;\n\n await gamepad.vibrationActuator.playEffect('dual-rumble', {\n startDelay: 0,\n duration: Math.max(25, duration),\n weakMagnitude: Math.min(1, Math.max(0, weakMagnitude)),\n strongMagnitude: Math.min(1, Math.max(0, strongMagnitude)),\n });\n }\n\n private _delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -76,4 +76,48 @@ declare class GamepadHapticAdapter implements HapticAdapter {
|
|
|
76
76
|
private _delay;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
/**
|
|
80
|
+
* SpatialHaptics — directional haptic feedback for dual-motor controllers.
|
|
81
|
+
*
|
|
82
|
+
* Provides left/right motor control, sweeps, pulses, impacts,
|
|
83
|
+
* and engine RPM simulation for immersive spatial feedback.
|
|
84
|
+
*/
|
|
85
|
+
declare class SpatialHaptics {
|
|
86
|
+
private adapter;
|
|
87
|
+
constructor(adapter: GamepadHapticAdapter);
|
|
88
|
+
/** Vibrate left motor only (weak/high-frequency motor) */
|
|
89
|
+
left(intensity?: number, duration?: number): Promise<void>;
|
|
90
|
+
/** Vibrate right motor only (strong/low-frequency motor) */
|
|
91
|
+
right(intensity?: number, duration?: number): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Sweep vibration from one side to the other.
|
|
94
|
+
* Plays first side then second side with slight overlap.
|
|
95
|
+
*/
|
|
96
|
+
sweep(direction: 'left-to-right' | 'right-to-left', duration?: number): Promise<void>;
|
|
97
|
+
/** Pulsing haptic on specified side */
|
|
98
|
+
pulse(side?: 'left' | 'right' | 'both', count?: number, interval?: number): Promise<void>;
|
|
99
|
+
/** Sustained left rumble */
|
|
100
|
+
rumbleLeft(duration?: number, intensity?: number): Promise<void>;
|
|
101
|
+
/** Sustained right rumble */
|
|
102
|
+
rumbleRight(duration?: number, intensity?: number): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Directional impact feedback.
|
|
105
|
+
* Center hits both motors simultaneously.
|
|
106
|
+
*/
|
|
107
|
+
impact(direction: 'left' | 'right' | 'center', force?: number): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Engine RPM simulation.
|
|
110
|
+
* Low RPM = slow weak pulses, high RPM = fast strong pulses.
|
|
111
|
+
* RPM range: 0-8000.
|
|
112
|
+
*/
|
|
113
|
+
engine(rpm: number): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Play a dual-rumble effect with explicit weak/strong magnitudes.
|
|
116
|
+
* Uses the adapter's pulse method internally, but crafts the sequence
|
|
117
|
+
* to target specific motors via the gamepad vibrationActuator API.
|
|
118
|
+
*/
|
|
119
|
+
private _playDualRumble;
|
|
120
|
+
private _delay;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { type DualMotorParams, type GamepadAdapterOptions, GamepadHapticAdapter, GamepadManager, type MotorMappingFn, SpatialHaptics, defaultMotorMapping, equalMotorMapping, heavyMotorMapping };
|
package/dist/index.d.ts
CHANGED
|
@@ -76,4 +76,48 @@ declare class GamepadHapticAdapter implements HapticAdapter {
|
|
|
76
76
|
private _delay;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
/**
|
|
80
|
+
* SpatialHaptics — directional haptic feedback for dual-motor controllers.
|
|
81
|
+
*
|
|
82
|
+
* Provides left/right motor control, sweeps, pulses, impacts,
|
|
83
|
+
* and engine RPM simulation for immersive spatial feedback.
|
|
84
|
+
*/
|
|
85
|
+
declare class SpatialHaptics {
|
|
86
|
+
private adapter;
|
|
87
|
+
constructor(adapter: GamepadHapticAdapter);
|
|
88
|
+
/** Vibrate left motor only (weak/high-frequency motor) */
|
|
89
|
+
left(intensity?: number, duration?: number): Promise<void>;
|
|
90
|
+
/** Vibrate right motor only (strong/low-frequency motor) */
|
|
91
|
+
right(intensity?: number, duration?: number): Promise<void>;
|
|
92
|
+
/**
|
|
93
|
+
* Sweep vibration from one side to the other.
|
|
94
|
+
* Plays first side then second side with slight overlap.
|
|
95
|
+
*/
|
|
96
|
+
sweep(direction: 'left-to-right' | 'right-to-left', duration?: number): Promise<void>;
|
|
97
|
+
/** Pulsing haptic on specified side */
|
|
98
|
+
pulse(side?: 'left' | 'right' | 'both', count?: number, interval?: number): Promise<void>;
|
|
99
|
+
/** Sustained left rumble */
|
|
100
|
+
rumbleLeft(duration?: number, intensity?: number): Promise<void>;
|
|
101
|
+
/** Sustained right rumble */
|
|
102
|
+
rumbleRight(duration?: number, intensity?: number): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Directional impact feedback.
|
|
105
|
+
* Center hits both motors simultaneously.
|
|
106
|
+
*/
|
|
107
|
+
impact(direction: 'left' | 'right' | 'center', force?: number): Promise<void>;
|
|
108
|
+
/**
|
|
109
|
+
* Engine RPM simulation.
|
|
110
|
+
* Low RPM = slow weak pulses, high RPM = fast strong pulses.
|
|
111
|
+
* RPM range: 0-8000.
|
|
112
|
+
*/
|
|
113
|
+
engine(rpm: number): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Play a dual-rumble effect with explicit weak/strong magnitudes.
|
|
116
|
+
* Uses the adapter's pulse method internally, but crafts the sequence
|
|
117
|
+
* to target specific motors via the gamepad vibrationActuator API.
|
|
118
|
+
*/
|
|
119
|
+
private _playDualRumble;
|
|
120
|
+
private _delay;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export { type DualMotorParams, type GamepadAdapterOptions, GamepadHapticAdapter, GamepadManager, type MotorMappingFn, SpatialHaptics, defaultMotorMapping, equalMotorMapping, heavyMotorMapping };
|
package/dist/index.js
CHANGED
|
@@ -153,6 +153,118 @@ var GamepadHapticAdapter = class {
|
|
|
153
153
|
}
|
|
154
154
|
};
|
|
155
155
|
|
|
156
|
-
|
|
156
|
+
// src/spatial/spatial-haptics.ts
|
|
157
|
+
var SpatialHaptics = class {
|
|
158
|
+
constructor(adapter) {
|
|
159
|
+
this.adapter = adapter;
|
|
160
|
+
}
|
|
161
|
+
// ─── Directional ─────────────────────────────────────────
|
|
162
|
+
/** Vibrate left motor only (weak/high-frequency motor) */
|
|
163
|
+
async left(intensity = 0.7, duration = 50) {
|
|
164
|
+
duration = Math.max(25, duration);
|
|
165
|
+
await this._playDualRumble(intensity, 0, duration);
|
|
166
|
+
}
|
|
167
|
+
/** Vibrate right motor only (strong/low-frequency motor) */
|
|
168
|
+
async right(intensity = 0.7, duration = 50) {
|
|
169
|
+
duration = Math.max(25, duration);
|
|
170
|
+
await this._playDualRumble(0, intensity, duration);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Sweep vibration from one side to the other.
|
|
174
|
+
* Plays first side then second side with slight overlap.
|
|
175
|
+
*/
|
|
176
|
+
async sweep(direction, duration = 200) {
|
|
177
|
+
duration = Math.max(50, duration);
|
|
178
|
+
const halfDuration = Math.max(25, Math.floor(duration / 2));
|
|
179
|
+
if (direction === "left-to-right") {
|
|
180
|
+
await this._playDualRumble(0.8, 0, halfDuration);
|
|
181
|
+
await this._delay(Math.max(25, halfDuration - 25));
|
|
182
|
+
await this._playDualRumble(0, 0.8, halfDuration);
|
|
183
|
+
} else {
|
|
184
|
+
await this._playDualRumble(0, 0.8, halfDuration);
|
|
185
|
+
await this._delay(Math.max(25, halfDuration - 25));
|
|
186
|
+
await this._playDualRumble(0.8, 0, halfDuration);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** Pulsing haptic on specified side */
|
|
190
|
+
async pulse(side = "both", count = 3, interval = 100) {
|
|
191
|
+
interval = Math.max(50, interval);
|
|
192
|
+
const pulseDuration = Math.max(25, Math.floor(interval * 0.4));
|
|
193
|
+
for (let i = 0; i < count; i++) {
|
|
194
|
+
if (side === "left") {
|
|
195
|
+
await this._playDualRumble(0.7, 0, pulseDuration);
|
|
196
|
+
} else if (side === "right") {
|
|
197
|
+
await this._playDualRumble(0, 0.7, pulseDuration);
|
|
198
|
+
} else {
|
|
199
|
+
await this._playDualRumble(0.7, 0.7, pulseDuration);
|
|
200
|
+
}
|
|
201
|
+
if (i < count - 1) {
|
|
202
|
+
await this._delay(interval - pulseDuration);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
// ─── Rumble ──────────────────────────────────────────────
|
|
207
|
+
/** Sustained left rumble */
|
|
208
|
+
async rumbleLeft(duration = 300, intensity = 0.6) {
|
|
209
|
+
duration = Math.max(25, duration);
|
|
210
|
+
await this._playDualRumble(intensity, 0, duration);
|
|
211
|
+
}
|
|
212
|
+
/** Sustained right rumble */
|
|
213
|
+
async rumbleRight(duration = 300, intensity = 0.6) {
|
|
214
|
+
duration = Math.max(25, duration);
|
|
215
|
+
await this._playDualRumble(0, intensity, duration);
|
|
216
|
+
}
|
|
217
|
+
// ─── Impact ──────────────────────────────────────────────
|
|
218
|
+
/**
|
|
219
|
+
* Directional impact feedback.
|
|
220
|
+
* Center hits both motors simultaneously.
|
|
221
|
+
*/
|
|
222
|
+
async impact(direction, force = 0.9) {
|
|
223
|
+
const impactDuration = 50;
|
|
224
|
+
if (direction === "left") {
|
|
225
|
+
await this._playDualRumble(force, force * 0.2, impactDuration);
|
|
226
|
+
} else if (direction === "right") {
|
|
227
|
+
await this._playDualRumble(force * 0.2, force, impactDuration);
|
|
228
|
+
} else {
|
|
229
|
+
await this._playDualRumble(force, force, impactDuration);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// ─── Simulation ──────────────────────────────────────────
|
|
233
|
+
/**
|
|
234
|
+
* Engine RPM simulation.
|
|
235
|
+
* Low RPM = slow weak pulses, high RPM = fast strong pulses.
|
|
236
|
+
* RPM range: 0-8000.
|
|
237
|
+
*/
|
|
238
|
+
async engine(rpm) {
|
|
239
|
+
const normalizedRPM = Math.min(8e3, Math.max(0, rpm));
|
|
240
|
+
const rpmFactor = normalizedRPM / 8e3;
|
|
241
|
+
const weakMag = Math.min(1, 0.2 + rpmFactor * 0.5);
|
|
242
|
+
const strongMag = Math.min(1, rpmFactor * 0.9);
|
|
243
|
+
const pulseDuration = Math.max(25, Math.floor(150 - rpmFactor * 120));
|
|
244
|
+
await this._playDualRumble(weakMag, strongMag, pulseDuration);
|
|
245
|
+
}
|
|
246
|
+
// ─── Internal ────────────────────────────────────────────
|
|
247
|
+
/**
|
|
248
|
+
* Play a dual-rumble effect with explicit weak/strong magnitudes.
|
|
249
|
+
* Uses the adapter's pulse method internally, but crafts the sequence
|
|
250
|
+
* to target specific motors via the gamepad vibrationActuator API.
|
|
251
|
+
*/
|
|
252
|
+
async _playDualRumble(weakMagnitude, strongMagnitude, duration) {
|
|
253
|
+
const manager = this.adapter.getManager();
|
|
254
|
+
const gamepad = manager.getFirstGamepad();
|
|
255
|
+
if (!gamepad?.vibrationActuator) return;
|
|
256
|
+
await gamepad.vibrationActuator.playEffect("dual-rumble", {
|
|
257
|
+
startDelay: 0,
|
|
258
|
+
duration: Math.max(25, duration),
|
|
259
|
+
weakMagnitude: Math.min(1, Math.max(0, weakMagnitude)),
|
|
260
|
+
strongMagnitude: Math.min(1, Math.max(0, strongMagnitude))
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
_delay(ms) {
|
|
264
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
export { GamepadHapticAdapter, GamepadManager, SpatialHaptics, defaultMotorMapping, equalMotorMapping, heavyMotorMapping };
|
|
157
269
|
//# sourceMappingURL=index.js.map
|
|
158
270
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/gamepad-manager.ts","../src/utils/dual-motor.ts","../src/adapters/gamepad-haptic.adapter.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** Manages gamepad connection state and provides access to connected gamepads */\nexport class GamepadManager {\n private _onConnect?: (gamepad: Gamepad) => void;\n private _onDisconnect?: (gamepad: Gamepad) => void;\n private _listening = false;\n\n /** Start listening for gamepad connections */\n listen(): void {\n if (this._listening || typeof window === 'undefined') return;\n this._listening = true;\n\n window.addEventListener('gamepadconnected', this._handleConnect);\n window.addEventListener('gamepaddisconnected', this._handleDisconnect);\n }\n\n /** Stop listening */\n unlisten(): void {\n if (!this._listening || typeof window === 'undefined') return;\n this._listening = false;\n\n window.removeEventListener('gamepadconnected', this._handleConnect);\n window.removeEventListener('gamepaddisconnected', this._handleDisconnect);\n }\n\n /** Get all currently connected gamepads */\n getGamepads(): Gamepad[] {\n if (typeof navigator === 'undefined' || !('getGamepads' in navigator)) {\n return [];\n }\n return Array.from(navigator.getGamepads()).filter(\n (gp): gp is Gamepad => gp !== null\n );\n }\n\n /** Get the first connected gamepad, or null */\n getFirstGamepad(): Gamepad | null {\n const gamepads = this.getGamepads();\n return gamepads[0] ?? null;\n }\n\n /** Set connection callback */\n onConnect(fn: (gamepad: Gamepad) => void): void {\n this._onConnect = fn;\n }\n\n /** Set disconnection callback */\n onDisconnect(fn: (gamepad: Gamepad) => void): void {\n this._onDisconnect = fn;\n }\n\n /** Check if any gamepad with haptic support is connected */\n hasHapticGamepad(): boolean {\n return this.getGamepads().some(\n (gp) => gp.vibrationActuator != null\n );\n }\n\n dispose(): void {\n this.unlisten();\n this._onConnect = undefined;\n this._onDisconnect = undefined;\n }\n\n private _handleConnect = (e: GamepadEvent) => {\n this._onConnect?.(e.gamepad);\n };\n\n private _handleDisconnect = (e: GamepadEvent) => {\n this._onDisconnect?.(e.gamepad);\n };\n}\n","/** Dual motor parameters for gamepad haptics */\nexport interface DualMotorParams {\n /** Weak (high-frequency) motor magnitude 0-1 */\n weakMagnitude: number;\n /** Strong (low-frequency) motor magnitude 0-1 */\n strongMagnitude: number;\n}\n\nexport type MotorMappingFn = (intensity: number) => DualMotorParams;\n\n/**\n * Default motor mapping: light effects use weak motor, heavy use strong, medium uses both.\n */\nexport const defaultMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: Math.min(intensity * 1.5, 1.0),\n strongMagnitude: Math.max(intensity - 0.3, 0),\n };\n};\n\n/**\n * Equal mapping: both motors at same intensity.\n */\nexport const equalMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: intensity,\n strongMagnitude: intensity,\n };\n};\n\n/**\n * Heavy mapping: emphasizes the strong (low-frequency) motor.\n */\nexport const heavyMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: intensity * 0.3,\n strongMagnitude: intensity,\n };\n};\n","import type { HapticAdapter, AdapterCapabilities, HapticStep } from '@hapticjs/core';\nimport { GamepadManager } from '../utils/gamepad-manager';\nimport { defaultMotorMapping } from '../utils/dual-motor';\nimport type { MotorMappingFn } from '../utils/dual-motor';\n\nexport interface GamepadAdapterOptions {\n /** Which gamepad index to use (default: first connected) */\n gamepadIndex?: number;\n /** Custom motor mapping function */\n motorMapping?: MotorMappingFn;\n /** Auto-listen for gamepad connections (default: true) */\n autoListen?: boolean;\n}\n\n/**\n * Gamepad haptics adapter using the GamepadHapticActuator API.\n * Supports dual-motor (weak/strong) vibration on modern controllers.\n */\nexport class GamepadHapticAdapter implements HapticAdapter {\n readonly name = 'gamepad';\n private manager: GamepadManager;\n private motorMapping: MotorMappingFn;\n private gamepadIndex?: number;\n private _cancelled = false;\n\n constructor(options: GamepadAdapterOptions = {}) {\n this.manager = new GamepadManager();\n this.motorMapping = options.motorMapping ?? defaultMotorMapping;\n this.gamepadIndex = options.gamepadIndex;\n\n if (options.autoListen !== false) {\n this.manager.listen();\n }\n }\n\n get supported(): boolean {\n return this.manager.hasHapticGamepad();\n }\n\n capabilities(): AdapterCapabilities {\n return {\n maxIntensityLevels: 100,\n minDuration: 1,\n maxDuration: 5000,\n supportsPattern: false,\n supportsIntensity: true,\n dualMotor: true,\n };\n }\n\n async pulse(intensity: number, duration: number): Promise<void> {\n const gamepad = this._getGamepad();\n if (!gamepad?.vibrationActuator) return;\n\n const { weakMagnitude, strongMagnitude } = this.motorMapping(intensity);\n\n await gamepad.vibrationActuator.playEffect('dual-rumble', {\n startDelay: 0,\n duration,\n weakMagnitude,\n strongMagnitude,\n });\n }\n\n async playSequence(steps: HapticStep[]): Promise<void> {\n this._cancelled = false;\n\n for (const step of steps) {\n if (this._cancelled) break;\n\n if (step.type === 'vibrate' && step.intensity > 0) {\n await this.pulse(step.intensity, step.duration);\n await this._delay(step.duration);\n } else {\n await this._delay(step.duration);\n }\n }\n }\n\n cancel(): void {\n this._cancelled = true;\n const gamepad = this._getGamepad();\n if (gamepad?.vibrationActuator) {\n gamepad.vibrationActuator.reset();\n }\n }\n\n dispose(): void {\n this.cancel();\n this.manager.dispose();\n }\n\n /** Get the gamepad manager for advanced usage */\n getManager(): GamepadManager {\n return this.manager;\n }\n\n private _getGamepad(): Gamepad | null {\n if (this.gamepadIndex !== undefined) {\n const gamepads = this.manager.getGamepads();\n return gamepads.find((gp) => gp.index === this.gamepadIndex) ?? null;\n }\n return this.manager.getFirstGamepad();\n }\n\n private _delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/gamepad-manager.ts","../src/utils/dual-motor.ts","../src/adapters/gamepad-haptic.adapter.ts","../src/spatial/spatial-haptics.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/** Manages gamepad connection state and provides access to connected gamepads */\nexport class GamepadManager {\n private _onConnect?: (gamepad: Gamepad) => void;\n private _onDisconnect?: (gamepad: Gamepad) => void;\n private _listening = false;\n\n /** Start listening for gamepad connections */\n listen(): void {\n if (this._listening || typeof window === 'undefined') return;\n this._listening = true;\n\n window.addEventListener('gamepadconnected', this._handleConnect);\n window.addEventListener('gamepaddisconnected', this._handleDisconnect);\n }\n\n /** Stop listening */\n unlisten(): void {\n if (!this._listening || typeof window === 'undefined') return;\n this._listening = false;\n\n window.removeEventListener('gamepadconnected', this._handleConnect);\n window.removeEventListener('gamepaddisconnected', this._handleDisconnect);\n }\n\n /** Get all currently connected gamepads */\n getGamepads(): Gamepad[] {\n if (typeof navigator === 'undefined' || !('getGamepads' in navigator)) {\n return [];\n }\n return Array.from(navigator.getGamepads()).filter(\n (gp): gp is Gamepad => gp !== null\n );\n }\n\n /** Get the first connected gamepad, or null */\n getFirstGamepad(): Gamepad | null {\n const gamepads = this.getGamepads();\n return gamepads[0] ?? null;\n }\n\n /** Set connection callback */\n onConnect(fn: (gamepad: Gamepad) => void): void {\n this._onConnect = fn;\n }\n\n /** Set disconnection callback */\n onDisconnect(fn: (gamepad: Gamepad) => void): void {\n this._onDisconnect = fn;\n }\n\n /** Check if any gamepad with haptic support is connected */\n hasHapticGamepad(): boolean {\n return this.getGamepads().some(\n (gp) => gp.vibrationActuator != null\n );\n }\n\n dispose(): void {\n this.unlisten();\n this._onConnect = undefined;\n this._onDisconnect = undefined;\n }\n\n private _handleConnect = (e: GamepadEvent) => {\n this._onConnect?.(e.gamepad);\n };\n\n private _handleDisconnect = (e: GamepadEvent) => {\n this._onDisconnect?.(e.gamepad);\n };\n}\n","/** Dual motor parameters for gamepad haptics */\nexport interface DualMotorParams {\n /** Weak (high-frequency) motor magnitude 0-1 */\n weakMagnitude: number;\n /** Strong (low-frequency) motor magnitude 0-1 */\n strongMagnitude: number;\n}\n\nexport type MotorMappingFn = (intensity: number) => DualMotorParams;\n\n/**\n * Default motor mapping: light effects use weak motor, heavy use strong, medium uses both.\n */\nexport const defaultMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: Math.min(intensity * 1.5, 1.0),\n strongMagnitude: Math.max(intensity - 0.3, 0),\n };\n};\n\n/**\n * Equal mapping: both motors at same intensity.\n */\nexport const equalMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: intensity,\n strongMagnitude: intensity,\n };\n};\n\n/**\n * Heavy mapping: emphasizes the strong (low-frequency) motor.\n */\nexport const heavyMotorMapping: MotorMappingFn = (intensity: number): DualMotorParams => {\n return {\n weakMagnitude: intensity * 0.3,\n strongMagnitude: intensity,\n };\n};\n","import type { HapticAdapter, AdapterCapabilities, HapticStep } from '@hapticjs/core';\nimport { GamepadManager } from '../utils/gamepad-manager';\nimport { defaultMotorMapping } from '../utils/dual-motor';\nimport type { MotorMappingFn } from '../utils/dual-motor';\n\nexport interface GamepadAdapterOptions {\n /** Which gamepad index to use (default: first connected) */\n gamepadIndex?: number;\n /** Custom motor mapping function */\n motorMapping?: MotorMappingFn;\n /** Auto-listen for gamepad connections (default: true) */\n autoListen?: boolean;\n}\n\n/**\n * Gamepad haptics adapter using the GamepadHapticActuator API.\n * Supports dual-motor (weak/strong) vibration on modern controllers.\n */\nexport class GamepadHapticAdapter implements HapticAdapter {\n readonly name = 'gamepad';\n private manager: GamepadManager;\n private motorMapping: MotorMappingFn;\n private gamepadIndex?: number;\n private _cancelled = false;\n\n constructor(options: GamepadAdapterOptions = {}) {\n this.manager = new GamepadManager();\n this.motorMapping = options.motorMapping ?? defaultMotorMapping;\n this.gamepadIndex = options.gamepadIndex;\n\n if (options.autoListen !== false) {\n this.manager.listen();\n }\n }\n\n get supported(): boolean {\n return this.manager.hasHapticGamepad();\n }\n\n capabilities(): AdapterCapabilities {\n return {\n maxIntensityLevels: 100,\n minDuration: 1,\n maxDuration: 5000,\n supportsPattern: false,\n supportsIntensity: true,\n dualMotor: true,\n };\n }\n\n async pulse(intensity: number, duration: number): Promise<void> {\n const gamepad = this._getGamepad();\n if (!gamepad?.vibrationActuator) return;\n\n const { weakMagnitude, strongMagnitude } = this.motorMapping(intensity);\n\n await gamepad.vibrationActuator.playEffect('dual-rumble', {\n startDelay: 0,\n duration,\n weakMagnitude,\n strongMagnitude,\n });\n }\n\n async playSequence(steps: HapticStep[]): Promise<void> {\n this._cancelled = false;\n\n for (const step of steps) {\n if (this._cancelled) break;\n\n if (step.type === 'vibrate' && step.intensity > 0) {\n await this.pulse(step.intensity, step.duration);\n await this._delay(step.duration);\n } else {\n await this._delay(step.duration);\n }\n }\n }\n\n cancel(): void {\n this._cancelled = true;\n const gamepad = this._getGamepad();\n if (gamepad?.vibrationActuator) {\n gamepad.vibrationActuator.reset();\n }\n }\n\n dispose(): void {\n this.cancel();\n this.manager.dispose();\n }\n\n /** Get the gamepad manager for advanced usage */\n getManager(): GamepadManager {\n return this.manager;\n }\n\n private _getGamepad(): Gamepad | null {\n if (this.gamepadIndex !== undefined) {\n const gamepads = this.manager.getGamepads();\n return gamepads.find((gp) => gp.index === this.gamepadIndex) ?? null;\n }\n return this.manager.getFirstGamepad();\n }\n\n private _delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n","import type { GamepadHapticAdapter } from '../adapters/gamepad-haptic.adapter';\n\n/**\n * SpatialHaptics — directional haptic feedback for dual-motor controllers.\n *\n * Provides left/right motor control, sweeps, pulses, impacts,\n * and engine RPM simulation for immersive spatial feedback.\n */\nexport class SpatialHaptics {\n private adapter: GamepadHapticAdapter;\n\n constructor(adapter: GamepadHapticAdapter) {\n this.adapter = adapter;\n }\n\n // ─── Directional ─────────────────────────────────────────\n\n /** Vibrate left motor only (weak/high-frequency motor) */\n async left(intensity = 0.7, duration = 50): Promise<void> {\n duration = Math.max(25, duration);\n await this._playDualRumble(intensity, 0, duration);\n }\n\n /** Vibrate right motor only (strong/low-frequency motor) */\n async right(intensity = 0.7, duration = 50): Promise<void> {\n duration = Math.max(25, duration);\n await this._playDualRumble(0, intensity, duration);\n }\n\n /**\n * Sweep vibration from one side to the other.\n * Plays first side then second side with slight overlap.\n */\n async sweep(\n direction: 'left-to-right' | 'right-to-left',\n duration = 200,\n ): Promise<void> {\n duration = Math.max(50, duration);\n const halfDuration = Math.max(25, Math.floor(duration / 2));\n\n if (direction === 'left-to-right') {\n await this._playDualRumble(0.8, 0, halfDuration);\n await this._delay(Math.max(25, halfDuration - 25));\n await this._playDualRumble(0, 0.8, halfDuration);\n } else {\n await this._playDualRumble(0, 0.8, halfDuration);\n await this._delay(Math.max(25, halfDuration - 25));\n await this._playDualRumble(0.8, 0, halfDuration);\n }\n }\n\n /** Pulsing haptic on specified side */\n async pulse(\n side: 'left' | 'right' | 'both' = 'both',\n count = 3,\n interval = 100,\n ): Promise<void> {\n interval = Math.max(50, interval);\n const pulseDuration = Math.max(25, Math.floor(interval * 0.4));\n\n for (let i = 0; i < count; i++) {\n if (side === 'left') {\n await this._playDualRumble(0.7, 0, pulseDuration);\n } else if (side === 'right') {\n await this._playDualRumble(0, 0.7, pulseDuration);\n } else {\n await this._playDualRumble(0.7, 0.7, pulseDuration);\n }\n\n if (i < count - 1) {\n await this._delay(interval - pulseDuration);\n }\n }\n }\n\n // ─── Rumble ──────────────────────────────────────────────\n\n /** Sustained left rumble */\n async rumbleLeft(duration = 300, intensity = 0.6): Promise<void> {\n duration = Math.max(25, duration);\n await this._playDualRumble(intensity, 0, duration);\n }\n\n /** Sustained right rumble */\n async rumbleRight(duration = 300, intensity = 0.6): Promise<void> {\n duration = Math.max(25, duration);\n await this._playDualRumble(0, intensity, duration);\n }\n\n // ─── Impact ──────────────────────────────────────────────\n\n /**\n * Directional impact feedback.\n * Center hits both motors simultaneously.\n */\n async impact(\n direction: 'left' | 'right' | 'center',\n force = 0.9,\n ): Promise<void> {\n const impactDuration = 50;\n\n if (direction === 'left') {\n await this._playDualRumble(force, force * 0.2, impactDuration);\n } else if (direction === 'right') {\n await this._playDualRumble(force * 0.2, force, impactDuration);\n } else {\n await this._playDualRumble(force, force, impactDuration);\n }\n }\n\n // ─── Simulation ──────────────────────────────────────────\n\n /**\n * Engine RPM simulation.\n * Low RPM = slow weak pulses, high RPM = fast strong pulses.\n * RPM range: 0-8000.\n */\n async engine(rpm: number): Promise<void> {\n const normalizedRPM = Math.min(8000, Math.max(0, rpm));\n const rpmFactor = normalizedRPM / 8000;\n\n // Map RPM to motor intensities\n const weakMag = Math.min(1, 0.2 + rpmFactor * 0.5);\n const strongMag = Math.min(1, rpmFactor * 0.9);\n\n // Map RPM to pulse duration (shorter at high RPM)\n const pulseDuration = Math.max(25, Math.floor(150 - rpmFactor * 120));\n\n await this._playDualRumble(weakMag, strongMag, pulseDuration);\n }\n\n // ─── Internal ────────────────────────────────────────────\n\n /**\n * Play a dual-rumble effect with explicit weak/strong magnitudes.\n * Uses the adapter's pulse method internally, but crafts the sequence\n * to target specific motors via the gamepad vibrationActuator API.\n */\n private async _playDualRumble(\n weakMagnitude: number,\n strongMagnitude: number,\n duration: number,\n ): Promise<void> {\n // Use the adapter's playSequence with a custom motor mapping\n // by directly accessing the adapter's internal gamepad\n const manager = this.adapter.getManager();\n const gamepad = manager.getFirstGamepad();\n if (!gamepad?.vibrationActuator) return;\n\n await gamepad.vibrationActuator.playEffect('dual-rumble', {\n startDelay: 0,\n duration: Math.max(25, duration),\n weakMagnitude: Math.min(1, Math.max(0, weakMagnitude)),\n strongMagnitude: Math.min(1, Math.max(0, strongMagnitude)),\n });\n }\n\n private _delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hapticjs/gamepad",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Gamepad haptics adapter for Feelback — dual motor support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -19,14 +19,15 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
|
-
"dist"
|
|
22
|
+
"dist",
|
|
23
|
+
"README.md"
|
|
23
24
|
],
|
|
24
25
|
"sideEffects": false,
|
|
25
26
|
"peerDependencies": {
|
|
26
|
-
"@hapticjs/core": "0.
|
|
27
|
+
"@hapticjs/core": "0.4.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
|
-
"@hapticjs/core": "0.
|
|
30
|
+
"@hapticjs/core": "0.4.0"
|
|
30
31
|
},
|
|
31
32
|
"keywords": [
|
|
32
33
|
"haptics",
|
|
@@ -36,6 +37,10 @@
|
|
|
36
37
|
"rumble",
|
|
37
38
|
"dual-motor"
|
|
38
39
|
],
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/thirumaleshp/Feelback.git"
|
|
43
|
+
},
|
|
39
44
|
"license": "MIT",
|
|
40
45
|
"scripts": {
|
|
41
46
|
"build": "tsup",
|