๐Ÿง  Haptics Fundamentals

๐ŸŽฏ The Science of Touch

Haptics is the science and technology of tactile and kinesthetic communication โ€” simulating the sense of touch. It encompasses tactile feedback (vibration, texture, pressure) and kinesthetic feedback (force, position, movement). Haptics is essential for immersive AR/VR, teleoperation, surgical robotics, and accessibility.

Why Haptics Matters

Presence: Touch completes the immersive experience. Information: Conveys texture, weight, temperature, stiffness. Feedback: Confirms actions (button press, collision). Accessibility: Enables communication for deafblind users. Training: Surgical simulation, skill acquisition. Teleoperation: Feel what a robot feels.

๐Ÿ“Œ Tactile๐Ÿ“Œ Kinesthetic๐Ÿ“Œ Force๐Ÿ“Œ Vibration
# Haptic feedback pipeline (conceptual) class HapticSystem: def __init__(self): self.actuators = [] # vibration motors, force feedback, etc. self.sensors = [] # position, force, touch sensors self.renderer = HapticRenderer() def update(self, dt): # 1. Read sensors (user input, collision) sensor_data = self.read_sensors() # 2. Compute haptic feedback feedback = self.renderer.compute(sensor_data) # 3. Send to actuators for actuator, signal in zip(self.actuators, feedback): actuator.set(signal) def render_contact(self, object_properties, interaction): """Render haptic feedback for contact with virtual object.""" stiffness = object_properties['stiffness'] friction = object_properties['friction'] penetration = interaction['penetration_depth'] # Spring-damper model force = stiffness * penetration - friction * interaction['velocity'] return force

๐Ÿ–๏ธ Human Touch Biology

๐ŸŽฏ How we sense touch

The human hand contains ~17,000 mechanoreceptors. Key types: Merkel cells (pressure), Meissner corpuscles (flutter), Pacinian corpuscles (vibration), Ruffini endings (stretch). Spatial acuity varies โ€” fingertips are most sensitive.

Mechanoreceptors

Pacinian: 20-1000 Hz, detects vibration. Meissner: 5-50 Hz, detects flutter/grip. Merkel: 0.5-15 Hz, detects pressure/texture. Ruffini: 0.5-15 Hz, detects stretch/skin deformation. Nociceptors: Pain. Thermoreceptors: Temperature. Two-point discrimination: fingertips ~2mm, palm ~10mm, back ~40mm.

๐Ÿ“ณ Pacinian๐ŸŒŠ Meissner๐Ÿ‘† Merkelโ†”๏ธ Ruffini
# Mechanoreceptor models for haptic rendering import numpy as np class MechanoreceptorModel: def __init__(self): # Adaptation rates and frequency sensitivities self.receptors = { 'pacinian': {'adaptation': 'fast', 'freq_range': (20, 1000), 'threshold': 0.001}, 'meissner': {'adaptation': 'fast', 'freq_range': (5, 50), 'threshold': 0.01}, 'merkel': {'adaptation': 'slow', 'freq_range': (0.5, 15), 'threshold': 0.02}, 'ruffini': {'adaptation': 'slow', 'freq_range': (0.5, 15), 'threshold': 0.05} } def compute_activation(self, stimulus_freq, stimulus_amp): """Compute receptor activation for a given stimulus.""" activations = {} for name, props in self.receptors.items(): f_min, f_max = props['freq_range'] if f_min <= stimulus_freq <= f_max: # Gaussian tuning curve center = (f_min + f_max) / 2 bandwidth = (f_max - f_min) / 2 tuning = np.exp(-((stimulus_freq - center) ** 2) / (2 * bandwidth ** 2)) # Threshold if stimulus_amp > props['threshold']: activations[name] = tuning * (stimulus_amp - props['threshold']) else: activations[name] = 0 else: activations[name] = 0 return activations # Two-point discrimination test def two_point_threshold(body_part): thresholds = { 'fingertip': 2.0, # mm 'palm': 10.0, 'forearm': 35.0, 'back': 40.0, 'calf': 45.0 } return thresholds.get(body_part, 40.0)

โš™๏ธ Actuators & Technologies

๐ŸŽฏ Creating touch sensations

Haptic actuators: ERM (Eccentric Rotating Mass), LRA (Linear Resonant Actuator), piezo, voice coils, electrostatic, ultrasonic, and pneumatic.

Actuator Comparison

ERM: Cheap, high amplitude, slow response. LRA: Fast response, crisp, low power (phones). Piezo: Very fast, high frequency, low amplitude. Voice coil: Wide frequency range, high fidelity. Electrostatic: Thin, texture rendering. Ultrasonic: Mid-air, no contact. Pneumatic: Force feedback, bulky.

๐ŸŒ€ ERMโšก LRA๐Ÿ”Š Piezo๐ŸŒŠ Ultrasonic
// Haptic actuator control (Arduino) #include // LRA control with DRV2605L #define DRV2605_ADDR 0x5A void setup() { Wire.begin(); // Initialize DRV2605 Wire.beginTransmission(DRV2605_ADDR); Wire.write(0x01); // Mode register Wire.write(0x00); // Internal trigger Wire.endTransmission(); // Set to LRA mode Wire.beginTransmission(DRV2605_ADDR); Wire.write(0x1A); // Feedback control Wire.write(0xB6); // LRA mode, closed loop Wire.endTransmission(); } void playEffect(uint8_t effect) { // Set waveform Wire.beginTransmission(DRV2605_ADDR); Wire.write(0x04); // Waveform sequence Wire.write(effect); Wire.endTransmission(); // Trigger Wire.beginTransmission(DRV2605_ADDR); Wire.write(0x0C); // GO register Wire.write(0x01); Wire.endTransmission(); } void loop() { playEffect(1); // Strong click delay(500); playEffect(47); // Buzz delay(500); } // Piezo haptic control (higher frequency) void playPiezo(float frequency, float amplitude, int duration_ms) { unsigned long start = millis(); while (millis() - start < duration_ms) { int period = 1000000 / frequency; digitalWrite(PIEZO_PIN, HIGH); delayMicroseconds(period / 2); digitalWrite(PIEZO_PIN, LOW); delayMicroseconds(period / 2); } }

๐Ÿ“ณ Vibration Haptics

๐ŸŽฏ The most common haptic feedback

Vibration haptics use ERM or LRA motors to create tactile sensations. It's ubiquitous in phones, controllers, and wearables. Techniques: amplitude modulation, frequency modulation, waveform design.

Vibration Design

Amplitude: Strength of vibration. Frequency: Speed of vibration (Hz). Duration: Length of vibration. Envelope: Attack, sustain, decay. Waveforms: Click, tick, buzz, pulse. HD Haptics: High-definition vibrations with rich textures (iPhone Taptic Engine, DualSense).

๐Ÿ“Š Amplitude๐ŸŽต Frequencyโฑ๏ธ Duration๐ŸŽจ Waveform
// Vibration patterns (Android) import android.os.Vibrator import android.os.VibrationEffect class HapticManager(private val vibrator: Vibrator) { fun playClick() { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { val effect = VibrationEffect.createOneShot(10, VibrationEffect.DEFAULT_AMPLITUDE) vibrator.vibrate(effect) } else { vibrator.vibrate(10) } } fun playPattern() { // Pattern: wait, vibrate, wait, vibrate val timings = longArrayOf(0, 100, 50, 100, 50, 200) val amplitudes = intArrayOf(0, 255, 0, 128, 0, 255) if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { val effect = VibrationEffect.createWaveform(timings, amplitudes, -1) vibrator.vibrate(effect) } } fun playHeartbeat() { // Heartbeat pattern: two pulses val timings = longArrayOf(0, 50, 100, 50, 500) val amplitudes = intArrayOf(0, 200, 0, 150, 0) val effect = VibrationEffect.createWaveform(timings, amplitudes, 0) // repeat vibrator.vibrate(effect) } } // iOS Core Haptics import CoreHaptics class HapticEngine { var engine: CHHapticEngine? func setup() { engine = try? CHHapticEngine() try? engine?.start() } func playTap() { let intensity = CHHapticEventParameter(parameterID: .hapticIntensity, value: 1.0) let sharpness = CHHapticEventParameter(parameterID: .hapticSharpness, value: 0.5) let event = CHHapticEvent(eventType: .hapticTransient, parameters: [intensity, sharpness], relativeTime: 0) let pattern = try? CHHapticPattern(events: [event], parameters: []) let player = try? engine?.makePlayer(with: pattern!) try? player?.start(atTime: 0) } }

๐Ÿฆพ Force Feedback

๐ŸŽฏ Resisting and assisting motion

Force feedback applies forces to the user's hand/body, simulating weight, stiffness, and resistance. Used in surgical robots, flight simulators, and haptic gloves.

Force Feedback Types

Impedance: Render force based on position (spring-damper). Admittance: Render motion based on force. Degrees of freedom: 1-DOF (steering wheel) to 6-DOF (robotic arm). Stiffness: How hard the virtual object feels. Damping: Resistance to motion. Inertia: Simulated mass.

๐Ÿฆพ Impedance๐Ÿ“ Admittance๐Ÿ”ฉ Stiffness๐Ÿ‹๏ธ Inertia
// Force feedback with spring-damper model class ForceFeedback { constructor() { this.stiffness = 500; // N/m this.damping = 10; // Ns/m this.virtualObjectPosition = { x: 0, y: 0, z: 0 }; this.virtualObjectRadius = 0.05; // 5cm } computeForce(handlePosition, handleVelocity) { // Distance from handle to object center const dx = handlePosition.x - this.virtualObjectPosition.x; const dy = handlePosition.y - this.virtualObjectPosition.y; const dz = handlePosition.z - this.virtualObjectPosition.z; const distance = Math.sqrt(dx*dx + dy*dy + dz*dz); // Check if inside object const penetration = this.virtualObjectRadius - distance; if (penetration > 0) { // Normal direction (from object center to handle) const nx = dx / distance; const ny = dy / distance; const nz = dz / distance; // Spring force const springForce = this.stiffness * penetration; // Damping force (opposes velocity) const dampingForce = -this.damping * ( handleVelocity.x * nx + handleVelocity.y * ny + handleVelocity.z * nz ); // Total force const totalForce = springForce + dampingForce; return { x: totalForce * nx, y: totalForce * ny, z: totalForce * nz }; } return { x: 0, y: 0, z: 0 }; } } // Haptic rendering loop at 1kHz const forceFeedback = new ForceFeedback(); const hapticLoop = setInterval(() => { const handlePos = getHandlePosition(); const handleVel = getHandleVelocity(); const force = forceFeedback.computeForce(handlePos, handleVel); setOutputForce(force); }, 1); // 1ms = 1kHz

๐ŸŒŠ Ultrasonic Mid-Air Haptics

๐ŸŽฏ Touching without touching

Ultrasonic mid-air haptics uses phased arrays of ultrasound transducers to create pressure points in the air. Users feel virtual objects without wearing anything. Used in automotive, public displays, and AR/VR.

How It Works

Phased array: 100+ ultrasonic transducers. Focal point: Constructive interference creates pressure. Modulation: Amplitude, frequency, spatiotemporal. Sensations: Point, line, circle, texture, vibration. Range: 10-30cm above array. Companies: Ultraleap (formerly Ultrahaptics).

๐Ÿ”Š Phased Array๐ŸŽฏ Focal Point๐ŸŒซ๏ธ Mid-Airโœ‹ No Contact
# Ultrasonic phased array simulation import numpy as np class UltrasonicArray: def __init__(self, num_transducers=256, frequency=40000): self.num_transducers = num_transducers self.frequency = frequency # 40 kHz self.wavelength = 343.0 / frequency # speed of sound / frequency self.transducers = self._arrange_transducers() def _arrange_transducers(self): """Arrange transducers in a circular pattern.""" transducers = [] for i in range(self.num_transducers): angle = 2 * np.pi * i / self.num_transducers radius = 0.1 # 10cm array radius x = radius * np.cos(angle) y = radius * np.sin(angle) transducers.append((x, y, 0)) return np.array(transducers) def compute_phases(self, focal_point): """Compute phase delays for each transducer to focus at focal_point.""" focal_point = np.array(focal_point) phases = [] for transducer in self.transducers: # Distance from transducer to focal point distance = np.linalg.norm(focal_point - transducer) # Phase delay = 2ฯ€ * distance / wavelength phase = (2 * np.pi * distance / self.wavelength) % (2 * np.pi) phases.append(phase) return np.array(phases) def compute_pressure_field(self, focal_point, resolution=50): """Compute pressure field in a plane above the array.""" phases = self.compute_phases(focal_point) # Sample points in a plane at z = focal_point[2] x = np.linspace(-0.1, 0.1, resolution) y = np.linspace(-0.1, 0.1, resolution) X, Y = np.meshgrid(x, y) Z = np.full_like(X, focal_point[2]) pressure = np.zeros_like(X, dtype=complex) for i, transducer in enumerate(self.transducers): # Distance from transducer to each point R = np.sqrt((X - transducer[0])**2 + (Y - transducer[1])**2 + (Z - transducer[2])**2) # Pressure contribution (simplified) amplitude = 1.0 / R # spherical spreading phase = 2 * np.pi * R / self.wavelength + phases[i] pressure += amplitude * np.exp(1j * phase) return np.abs(pressure) # Usage array = UltrasonicArray(num_transducers=256) focal_point = (0, 0, 0.15) # 15cm above center pressure_field = array.compute_pressure_field(focal_point) print(f"Max pressure: {pressure_field.max():.2f}") print(f"Focal point pressure: {pressure_field[25, 25]:.2f}")

๐ŸŒก๏ธ Thermal Haptics

๐ŸŽฏ Feeling hot and cold

Thermal haptics simulates temperature sensations using Peltier elements (thermoelectric coolers). It adds realism to virtual experiences โ€” feeling a cold drink, warm sun, or hot surface.

Thermal Feedback

Peltier: Heat pump โ€” one side hot, other cold. Range: 0-50ยฐC safe range. Response time: 1-5 seconds (slow). Applications: Virtual drinks, weather, material identification. Challenges: Slow, power-hungry, safety. Combined: Thermal + vibration for richer sensations.

๐Ÿ”ฅ Hotโ„๏ธ Coldโšก Peltier๐Ÿข Slow
// Thermal haptic control (Peltier + Arduino) #include #define PELTIER_PIN 9 #define TEMP_SENSOR_PIN A0 double setpoint = 25.0; // target temperature ยฐC double input, output; double Kp = 10, Ki = 0.5, Kd = 1; PID thermalPID(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT); void setup() { Serial.begin(9600); thermalPID.SetMode(AUTOMATIC); thermalPID.SetOutputLimits(-255, 255); // bidirectional thermalPID.SetSampleTime(100); // 10Hz } void loop() { // Read temperature int sensorValue = analogRead(TEMP_SENSOR_PIN); input = (sensorValue / 1024.0) * 500.0; // LM35: 10mV/ยฐC // Compute PID thermalPID.Compute(); // Drive Peltier (H-bridge for bidirectional) if (output > 0) { analogWrite(PELTIER_PIN, (int)output); // heating } else { analogWrite(PELTIER_PIN, (int)-output); // cooling } Serial.print("Temp: "); Serial.print(input); Serial.print("ยฐC, Output: "); Serial.println(output); delay(100); } // Thermal pattern: simulate cold drink void simulateColdDrink() { setpoint = 5.0; // cold delay(3000); setpoint = 25.0; // back to neutral } // Thermal pattern: simulate hot coffee void simulateHotCoffee() { setpoint = 45.0; // hot delay(2000); setpoint = 25.0; // back to neutral }

โšก Electrotactile Stimulation

๐ŸŽฏ Direct nerve stimulation

Electrotactile stimulation uses small electrical currents to stimulate touch receptors directly, creating sensations without mechanical actuators. Used in sensory substitution and haptic feedback.

Electrical Stimulation

TENS: Transcutaneous electrical nerve stimulation. Electrodes: On skin, stimulate afferent nerves. Sensations: Tingling, vibration, pressure, pain (if too strong). Frequency: 1-100 Hz. Applications: Sensory substitution (vision-to-touch), haptic feedback, pain relief. Safety: Current-limited, medical-grade.

โšก TENS๐Ÿง  Nerve๐Ÿ”Œ Electrode๐Ÿ“Š Frequency
// Electrotactile stimulation (safety-critical!) class ElectrotactileStimulator { constructor() { this.maxCurrent = 20; // mA (safety limit) this.maxFrequency = 100; // Hz this.pulseWidth = 200; // microseconds this.electrodes = []; } addElectrode(position) { this.electrodes.push({ position: position, active: false, current: 0 }); } stimulate(electrodeIndex, intensity, frequency) { // Safety checks if (intensity > this.maxCurrent) { throw new Error("Current exceeds safety limit"); } if (frequency > this.maxFrequency) { throw new Error("Frequency exceeds safety limit"); } const electrode = this.electrodes[electrodeIndex]; electrode.active = true; electrode.current = intensity; // Generate biphasic pulses (balanced charge) this.generatePulses(electrode, frequency); } generatePulses(electrode, frequency) { const period = 1000000 / frequency; // microseconds const interval = setInterval(() => { if (!electrode.active) { clearInterval(interval); return; } // Biphasic pulse: positive then negative this.applyCurrent(electrode, electrode.current, this.pulseWidth); setTimeout(() => { this.applyCurrent(electrode, -electrode.current, this.pulseWidth); }, this.pulseWidth); }, period); } stop(electrodeIndex) { this.electrodes[electrodeIndex].active = false; this.electrodes[electrodeIndex].current = 0; } } // Sensory substitution: vision to touch function visionToTouch(imageData, electrodeArray) { // Downsample image to electrode grid const gridSize = Math.sqrt(electrodeArray.length); const cellWidth = imageData.width / gridSize; const cellHeight = imageData.height / gridSize; for (let i = 0; i < electrodeArray.length; i++) { const row = Math.floor(i / gridSize); const col = i % gridSize; // Average brightness in cell let sum = 0, count = 0; for (let y = row * cellHeight; y < (row + 1) * cellHeight; y++) { for (let x = col * cellWidth; x < (col + 1) * cellWidth; x++) { const idx = (y * imageData.width + x) * 4; sum += (imageData.data[idx] + imageData.data[idx + 1] + imageData.data[idx + 2]) / 3; count++; } } const brightness = sum / count; // Map brightness to stimulation intensity const intensity = (brightness / 255) * 15; // max 15mA if (intensity > 1) { electrodeArray[i].stimulate(intensity, 50); } else { electrodeArray[i].stop(); } } }

๐Ÿงค Haptic Gloves & Exoskeletons

๐ŸŽฏ Wearing the virtual world

Haptic gloves and exoskeletons provide force feedback and tactile sensations to the hand and fingers. Used in VR training, teleoperation, and medical simulation.

Glove Technologies

Vibration gloves: Buzz on contact. Force feedback gloves: Resist finger movement (exoskeleton). Pneumatic gloves: Air bladders for pressure. Electrotactile gloves: Electrical stimulation. Thermal gloves: Hot/cold. Companies: HaptX, Dexmo, SenseGlove, Manus.

๐Ÿงค Vibration๐Ÿฆพ Exoskeleton๐Ÿ’จ Pneumaticโšก Electro
# Haptic glove control (Unity + SenseGlove) using UnityEngine; using SenseGloveCs; public class HapticGloveController : MonoBehaviour { public SenseGlove glove; public float forceMultiplier = 1.0f; void Start() { // Connect to glove SenseGloveCs.SenseGlove.Connect(); } void Update() { // Get finger positions for (int i = 0; i < 5; i++) { float flexion = glove.GetFingerFlexion(i); float abduction = glove.GetFingerAbduction(i); // Apply to virtual hand virtualHand.SetFinger(i, flexion, abduction); } // Check for collisions CheckCollisions(); } void CheckCollisions() { // For each fingertip, check collision with virtual object for (int i = 0; i < 5; i++) { Vector3 fingertipPos = virtualHand.GetFingertipPosition(i); if (Physics.CheckSphere(fingertipPos, 0.01f)) { // Collision detected โ€” compute force float penetration = ComputePenetration(fingertipPos); float force = penetration * 10.0f * forceMultiplier; // Send force feedback to glove glove.SetForceFeedback(i, force); // Play haptic effect glove.PlayHapticEffect(i, HapticEffect.Contact); } } } void OnDestroy() { SenseGloveCs.SenseGlove.Disconnect(); } } // Exoskeleton force feedback (Dexmo) public class ExoskeletonController : MonoBehaviour { public DexmoExoskeleton exoskeleton; void ApplyForce(int fingerIndex, float force) { // Force range: 0-5N force = Mathf.Clamp(force, 0, 5); exoskeleton.SetBrakeForce(fingerIndex, force); } void OnCollisionEnter(Collision collision) { // Determine which finger hit int finger = GetFingerFromCollider(collision.collider); // Compute force based on object stiffness float objectStiffness = collision.gameObject.GetComponent().stiffness; float penetration = collision.contacts[0].separation; float force = objectStiffness * Mathf.Abs(penetration); ApplyForce(finger, force); } }

๐ŸŽจ Haptic Rendering

๐ŸŽฏ Computing touch sensations

Haptic rendering computes forces and vibrations based on virtual object properties and user interaction. It requires high update rates (1kHz) for stable force feedback.

Haptic Rendering Pipeline

1. Collision detection: Find contacts (spheres, meshes). 2. Force computation: Spring-damper, penalty, constraint-based. 3. Force filtering: Smooth, stabilize. 4. Actuator control: Convert force to actuator signals. 5. Update rate: 1kHz for force, 100Hz for vibration. Stability: Critical โ€” oscillations if too stiff.

๐ŸŽฏ Collision๐Ÿ”ฉ Forceโšก 1kHz๐Ÿ“Š Stability
# Haptic rendering with god-object method import numpy as np class HapticRenderer: def __init__(self): self.stiffness = 1000.0 # N/m self.damping = 5.0 # Ns/m self.max_force = 10.0 # N (safety) def render(self, haptic_position, haptic_velocity, virtual_objects): """Render haptic feedback for the current haptic position.""" total_force = np.zeros(3) for obj in virtual_objects: force = self.compute_object_force(haptic_position, haptic_velocity, obj) total_force += force # Clamp force for safety force_magnitude = np.linalg.norm(total_force) if force_magnitude > self.max_force: total_force = total_force / force_magnitude * self.max_force return total_force def compute_object_force(self, position, velocity, obj): """Compute force from a single virtual object.""" if obj['type'] == 'sphere': return self.sphere_force(position, velocity, obj) elif obj['type'] == 'plane': return self.plane_force(position, velocity, obj) elif obj['type'] == 'mesh': return self.mesh_force(position, velocity, obj) return np.zeros(3) def sphere_force(self, position, velocity, sphere): """Force from a sphere object (spring-damper).""" center = np.array(sphere['center']) radius = sphere['radius'] # Vector from center to position delta = position - center distance = np.linalg.norm(delta) if distance < radius: # Inside sphere โ€” compute penetration penetration = radius - distance normal = delta / distance # outward normal # Spring force (pushes out) spring_force = self.stiffness * penetration * normal # Damping force (opposes velocity) damping_force = -self.damping * np.dot(velocity, normal) * normal return spring_force + damping_force return np.zeros(3) def plane_force(self, position, velocity, plane): """Force from a plane (e.g., floor, wall).""" point = np.array(plane['point']) normal = np.array(plane['normal']) normal = normal / np.linalg.norm(normal) # Signed distance to plane distance = np.dot(position - point, normal) if distance < 0: # Below plane โ€” penetration penetration = -distance # Spring force (pushes up) spring_force = self.stiffness * penetration * normal # Friction (opposes tangential velocity) tangential_velocity = velocity - np.dot(velocity, normal) * normal friction_force = -plane.get('friction', 0.5) * np.linalg.norm(spring_force) * tangential_velocity friction_force = np.clip(friction_force, -1, 1) # limit return spring_force + friction_force return np.zeros(3) # Haptic loop at 1kHz renderer = HapticRenderer() virtual_objects = [ {'type': 'sphere', 'center': [0.1, 0, 0], 'radius': 0.05}, {'type': 'plane', 'point': [0, -0.1, 0], 'normal': [0, 1, 0], 'friction': 0.6} ] def haptic_loop(): while True: position = get_haptic_position() velocity = get_haptic_velocity() force = renderer.render(position, velocity, virtual_objects) set_output_force(force) time.sleep(0.001) # 1kHz

๐Ÿงถ Texture & Material Rendering

๐ŸŽฏ Feeling surfaces

Texture rendering simulates surface properties: roughness, friction, bumps, and material feel. Techniques: vibrotactile (vibration patterns), electrostatic (friction modulation), ultrasonic (mid-air).

Texture Rendering Methods

Vibrotactile: Vibration frequency/amplitude encodes texture. Electrostatic: Modulates friction between finger and surface. Ultrasonic: Air pressure modulation. Data-driven: Record real textures, play back. Perceptual: Model based on human perception. Materials: Sandpaper, wood, metal, fabric, water.

๐Ÿ“ณ Vibrotactileโšก Electrostatic๐ŸŒŠ Ultrasonic๐ŸŽจ Data-driven
# Texture rendering with vibration patterns import numpy as np from scipy import signal class TextureRenderer: def __init__(self, sample_rate=1000): self.fs = sample_rate self.textures = {} def add_texture(self, name, roughness, friction, bumpiness): """Define a texture by its physical properties.""" self.textures[name] = { 'roughness': roughness, # 0-1 'friction': friction, # 0-1 'bumpiness': bumpiness, # 0-1 'pattern': self.generate_pattern(roughness, bumpiness) } def generate_pattern(self, roughness, bumpiness): """Generate vibration pattern for texture.""" # Roughness -> frequency (higher = rougher) freq = 20 + roughness * 200 # 20-220 Hz # Bumpiness -> amplitude modulation # Use noise for irregular bumps t = np.linspace(0, 1, self.fs) # Base vibration vibration = np.sin(2 * np.pi * freq * t) # Add noise for bumpiness noise = np.random.randn(len(t)) * bumpiness vibration += noise # Amplitude modulation (roughness variation) envelope = 1 + 0.5 * np.sin(2 * np.pi * 5 * t) # 5 Hz modulation vibration *= envelope return vibration def render(self, texture_name, velocity): """Render texture based on sliding velocity.""" if texture_name not in self.textures: return np.zeros(self.fs) texture = self.textures[texture_name] pattern = texture['pattern'] # Texture intensity depends on sliding velocity # (faster = more sensation) speed = np.linalg.norm(velocity) intensity = min(speed / 0.5, 1.0) # max at 0.5 m/s # Friction depends on texture friction_force = texture['friction'] * speed # Return vibration signal + friction return { 'vibration': pattern * intensity, 'friction_force': friction_force } # Usage renderer = TextureRenderer() renderer.add_texture('sandpaper', roughness=0.9, friction=0.8, bumpiness=0.7) renderer.add_texture('silk', roughness=0.1, friction=0.3, bumpiness=0.05) renderer.add_texture('wood', roughness=0.5, friction=0.5, bumpiness=0.3) renderer.add_texture('metal', roughness=0.2, friction=0.2, bumpiness=0.1) # Render while sliding finger velocity = np.array([0.3, 0, 0]) # sliding at 0.3 m/s feedback = renderer.render('sandpaper', velocity) print(f"Vibration amplitude: {np.max(feedback['vibration']):.2f}") print(f"Friction force: {feedback['friction_force']:.2f} N")

๐Ÿฅฝ Haptics in AR/VR

๐ŸŽฏ Completing the immersive experience

Haptics in AR/VR provides touch feedback for virtual objects: vibration when touching, force when grabbing, texture when sliding, temperature when interacting. It dramatically increases presence and immersion.

Haptic Feedback in XR

Controllers: Rumble on contact (Quest, Index, PSVR2). Hand tracking: Ultrasonic mid-air, gloves. Body: Haptic vests, suits (bHaptics, Teslasuit). Weapons: Recoil, reloading. Social: Virtual handshake, hug. Environment: Rain, wind, heat. Presence: Touch increases immersion by 40%+.

๐ŸŽฎ Controllers๐Ÿงค Gloves๐Ÿฆบ Vests๐ŸŒŠ Mid-Air
// Haptic feedback in Unity XR (XR Interaction Toolkit) using UnityEngine; using UnityEngine.XR.Interaction.Toolkit; public class HapticFeedback : MonoBehaviour { public XRBaseController leftController; public XRBaseController rightController; // Haptic presets public HapticPreset softTouch = new HapticPreset(0.2f, 0.05f); public HapticPreset hardImpact = new HapticPreset(1.0f, 0.1f); public HapticPreset textureVibration = new HapticPreset(0.3f, 0.02f); void OnCollisionEnter(Collision collision) { // Determine which hand collided XRBaseController controller = GetControllerForCollider(collision.collider); if (controller != null) { // Compute impact force float impactForce = collision.relativeVelocity.magnitude; // Select haptic preset based on force HapticPreset preset = impactForce > 2.0f ? hardImpact : softTouch; // Play haptic feedback controller.SendHapticImpulse(preset.amplitude, preset.duration); } } void OnTriggerStay(Collider other) { // Texture feedback while sliding if (other.CompareTag("Textured")) { float velocity = GetHandVelocity(); if (velocity > 0.1f) { // sliding XRBaseController controller = GetControllerForCollider(other); controller.SendHapticImpulse( textureVibration.amplitude * Mathf.Min(velocity, 1.0f), textureVibration.duration ); } } } XRBaseController GetControllerForCollider(Collider collider) { // Determine which hand based on collider position if (collider.transform.position.x < 0) { return leftController; } else { return rightController; } } } [System.Serializable] public struct HapticPreset { public float amplitude; // 0-1 public float duration; // seconds public HapticPreset(float amp, float dur) { amplitude = amp; duration = dur; } } // Haptic vest control (bHaptics) using Bhaptics.SDK2; public class HapticVest : MonoBehaviour { public string appId = "your-app-id"; public string apiKey = "your-api-key"; void Start() { BhapticsLibrary.Initialize(apiKey, appId, "https://api.bhaptics.com"); } public void PlayImpact(Vector3 position, float intensity) { // Map world position to vest motor // Vest has 40 motors (8x5 grid front + back) int motorIndex = WorldToVestMotor(position); // Play haptic BhapticsLibrary.PlayMotors((int)PositionType.Vest, new int[] { motorIndex }, (int)(intensity * 100), // duration ms 100); // intensity 0-100 } int WorldToVestMotor(Vector3 position) { // Simplified mapping float x = Mathf.InverseLerp(-1, 1, position.x); // left-right float y = Mathf.InverseLerp(0, 2, position.y); // bottom-top int col = Mathf.FloorToInt(x * 7); // 8 columns int row = Mathf.FloorToInt(y * 4); // 5 rows return row * 8 + col; } }

โš ๏ธ Challenges & Limitations

๐ŸŽฏ Why haptics is hard

Haptics faces unique challenges: high update rates, stability, actuator limitations, power consumption, wearability, and the complexity of human touch perception.

Key Challenges

Update rate: 1kHz needed for stable force feedback. Stability: Stiff surfaces cause oscillations. Actuator limits: Force, displacement, bandwidth. Power: Haptic gloves drain batteries fast. Wearability: Bulky, uncomfortable. Perception: Individual differences, adaptation. Safety: Electrical, thermal, mechanical. Cost: High-quality haptics is expensive.

โšก 1kHz๐Ÿ“Š Stability๐Ÿ”‹ Power๐Ÿงค Wearability
# Stability in haptic rendering import numpy as np class StableHapticRenderer: def __init__(self, stiffness=1000, damping=5, mass=0.1, dt=0.001): self.k = stiffness self.b = damping self.m = mass self.dt = dt # 1ms # Stability criterion for virtual coupling # k * dt^2 / m < 1 (simplified) self.max_stable_stiffness = mass / (dt ** 2) print(f"Max stable stiffness: {self.max_stable_stiffness:.0f} N/m") if stiffness > self.max_stable_stiffness: print(f"Warning: stiffness {stiffness} exceeds stability limit!") self.k = self.max_stable_stiffness * 0.9 def render(self, position, velocity, target_position): """Render force with stability guarantees.""" # Penetration penetration = np.linalg.norm(position - target_position) if penetration > 0: direction = (position - target_position) / penetration # Spring force spring_force = self.k * penetration * direction # Damping force damping_force = -self.b * velocity # Total force = spring_force + damping_force # Clamp for safety max_force = 20.0 # N force_mag = np.linalg.norm(force) if force_mag > max_force: force = force / force_mag * max_force return force return np.zeros(3) def check_passivity(self, energy_history): """Check passivity for stability.""" # Passivity: energy must not increase # E(t) = 0.5 * m * v^2 + 0.5 * k * x^2 # If energy increases, system is active (unstable) if len(energy_history) < 2: return True energy_increase = energy_history[-1] - energy_history[-2] return energy_increase <= 0 # Power consumption estimation def estimate_power(actuator_type, force, velocity, duty_cycle): """Estimate power consumption of haptic actuator.""" if actuator_type == 'ERM': # ERM: power proportional to voltage^2 voltage = force / 10.0 # simplified power = (voltage ** 2) / 10.0 # ohms return power * duty_cycle elif actuator_type == 'LRA': # LRA: more efficient power = force * velocity * 0.5 return power * duty_cycle elif actuator_type == 'piezo': # Piezo: high voltage, low current power = force * velocity * 0.3 return power * duty_cycle elif actuator_type == 'voice_coil': # Voice coil: high power power = force * velocity * 0.8 return power * duty_cycle return 0 # Battery life estimation def battery_life(battery_capacity_mAh, power_consumption_W, voltage=3.7): """Estimate battery life in hours.""" energy_Wh = battery_capacity_mAh * voltage / 1000 return energy_Wh / power_consumption_W # Example: Haptic glove with 5 LRA actuators power_per_actuator = estimate_power('LRA', force=0.5, velocity=0.1, duty_cycle=0.3) total_power = power_per_actuator * 5 print(f"Power per actuator: {power_per_actuator:.2f} W") print(f"Total power: {total_power:.2f} W") print(f"Battery life (2000mAh): {battery_life(2000, total_power):.1f} hours")

๐Ÿš€ Future: Neural Haptics

๐ŸŽฏ The future of touch

Neural haptics uses AI to create more realistic, personalized, and efficient touch sensations: neural rendering, predictive haptics, brain-computer interfaces, and soft robotics.

Neural Haptics Advances

Neural rendering: Deep learning generates haptic signals from visual/tactile data. Predictive: Anticipate contact before it happens. Personalized: Adapt to individual perception. BCI: Direct neural stimulation for touch. Soft robotics: Flexible, wearable, natural. Digital twins: Haptic models of real objects. Haptic internet: Touch over the network.

๐Ÿง  Neural๐Ÿ”ฎ Predictive๐Ÿงฌ BCI๐Ÿค– Soft Robotics
# Neural haptic rendering (PyTorch) import torch import torch.nn as nn class NeuralHapticRenderer(nn.Module): """Neural network that maps object properties + interaction to haptic actuator signals.""" def __init__(self, input_dim=10, hidden_dim=128, output_dim=5): super().__init__() self.network = nn.Sequential( nn.Linear(input_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, hidden_dim), nn.ReLU(), nn.Linear(hidden_dim, output_dim), nn.Tanh() # output in [-1, 1] ) def forward(self, x): # x: [object_stiffness, object_friction, object_roughness, # penetration, velocity_x, velocity_y, velocity_z, # user_grip_force, user_skin_temp, previous_haptic] return self.network(x) # Training data from real interactions class HapticDataset(torch.utils.data.Dataset): def __init__(self, data_path): # Load recorded data: object properties, interactions, user ratings self.data = torch.load(data_path) def __getitem__(self, idx): return self.data[idx] # Train neural haptic renderer model = NeuralHapticRenderer() optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) criterion = nn.MSELoss() for epoch in range(100): for batch in dataloader: input_features, target_haptics = batch # Predict predicted_haptics = model(input_features) # Loss loss = criterion(predicted_haptics, target_haptics) optimizer.zero_grad() loss.backward() optimizer.step() print(f"Epoch {epoch}: loss = {loss.item():.4f}") # Inference: predict haptic signals in real-time def render_haptics(object_props, interaction, user_state): features = torch.tensor([ object_props['stiffness'], object_props['friction'], object_props['roughness'], interaction['penetration'], interaction['velocity'][0], interaction['velocity'][1], interaction['velocity'][2], user_state['grip_force'], user_state['skin_temp'], 0.0 # previous haptic (feedback) ]) with torch.no_grad(): haptic_signals = model(features) # Map to actuators actuator_signals = { 'index_vibration': haptic_signals[0].item(), 'middle_vibration': haptic_signals[1].item(), 'ring_vibration': haptic_signals[2].item(), 'pinky_vibration': haptic_signals[3].item(), 'thumb_vibration': haptic_signals[4].item() } return actuator_signals # Example object_props = {'stiffness': 0.8, 'friction': 0.5, 'roughness': 0.7} interaction = {'penetration': 0.01, 'velocity': [0.1, 0, 0]} user_state = {'grip_force': 2.0, 'skin_temp': 32.0} actuators = render_haptics(object_props, interaction, user_state) print(actuators)