|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<meta name="description" content="Interactive Embodied AI Autonomous Vehicle Simulator - Real-time AI decision making and navigation"> |
|
<meta name="keywords" content="AI, autonomous vehicles, simulation, embodied AI, robotics, machine learning"> |
|
<meta name="author" content="Embodied AI Research"> |
|
<title>🚗 Embodied AI - Autonomous Vehicle Simulator</title> |
|
|
|
|
|
<meta property="og:title" content="Embodied AI Vehicle Simulator"> |
|
<meta property="og:description" content="Interactive demonstration of autonomous vehicle AI with real-time decision making"> |
|
<meta property="og:type" content="website"> |
|
<meta property="og:image" content="https://via.placeholder.com/1200x630/667eea/ffffff?text=Embodied+AI+Vehicle+Simulator"> |
|
|
|
|
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🚗</text></svg>"> |
|
|
|
<style> |
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', sans-serif; |
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
|
min-height: 100vh; |
|
display: flex; |
|
flex-direction: column; |
|
color: white; |
|
overflow-x: hidden; |
|
} |
|
|
|
.header { |
|
background: rgba(0, 0, 0, 0.3); |
|
padding: 20px; |
|
text-align: center; |
|
backdrop-filter: blur(10px); |
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1); |
|
position: relative; |
|
} |
|
|
|
.header h1 { |
|
font-size: clamp(1.8rem, 4vw, 2.5rem); |
|
margin-bottom: 10px; |
|
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3); |
|
background: linear-gradient(45deg, #fff, #e0e0e0); |
|
-webkit-background-clip: text; |
|
-webkit-text-fill-color: transparent; |
|
background-clip: text; |
|
} |
|
|
|
.header p { |
|
font-size: clamp(0.9rem, 2.5vw, 1.1rem); |
|
opacity: 0.9; |
|
} |
|
|
|
.hf-badge { |
|
position: absolute; |
|
top: 10px; |
|
right: 20px; |
|
background: rgba(255, 255, 255, 0.2); |
|
padding: 5px 10px; |
|
border-radius: 15px; |
|
font-size: 0.8rem; |
|
backdrop-filter: blur(5px); |
|
} |
|
|
|
.main-container { |
|
display: flex; |
|
flex: 1; |
|
gap: 20px; |
|
padding: 20px; |
|
max-width: 1400px; |
|
margin: 0 auto; |
|
width: 100%; |
|
} |
|
|
|
.simulation-area { |
|
flex: 1; |
|
background: rgba(255, 255, 255, 0.1); |
|
border-radius: 15px; |
|
padding: 20px; |
|
backdrop-filter: blur(10px); |
|
border: 1px solid rgba(255, 255, 255, 0.2); |
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.canvas-container { |
|
position: relative; |
|
width: 100%; |
|
height: 600px; |
|
background: #1a1a2e; |
|
border-radius: 10px; |
|
overflow: hidden; |
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); |
|
border: 2px solid rgba(255, 255, 255, 0.1); |
|
} |
|
|
|
#simulationCanvas { |
|
width: 100%; |
|
height: 100%; |
|
display: block; |
|
cursor: crosshair; |
|
} |
|
|
|
.controls-panel { |
|
width: 320px; |
|
background: rgba(255, 255, 255, 0.1); |
|
border-radius: 15px; |
|
padding: 20px; |
|
backdrop-filter: blur(10px); |
|
border: 1px solid rgba(255, 255, 255, 0.2); |
|
height: fit-content; |
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.control-section { |
|
margin-bottom: 25px; |
|
} |
|
|
|
.control-section h3 { |
|
font-size: 1.2rem; |
|
margin-bottom: 15px; |
|
color: #fff; |
|
border-bottom: 2px solid rgba(255, 255, 255, 0.3); |
|
padding-bottom: 8px; |
|
display: flex; |
|
align-items: center; |
|
gap: 8px; |
|
} |
|
|
|
.control-group { |
|
margin-bottom: 15px; |
|
} |
|
|
|
.control-group label { |
|
display: block; |
|
margin-bottom: 8px; |
|
font-weight: 500; |
|
color: #e0e0e0; |
|
font-size: 0.9rem; |
|
} |
|
|
|
.control-group input, .control-group select { |
|
width: 100%; |
|
padding: 10px 12px; |
|
border: none; |
|
border-radius: 8px; |
|
background: rgba(255, 255, 255, 0.2); |
|
color: white; |
|
font-size: 14px; |
|
transition: background 0.3s ease; |
|
} |
|
|
|
.control-group input:focus, .control-group select:focus { |
|
outline: 2px solid rgba(255, 255, 255, 0.5); |
|
background: rgba(255, 255, 255, 0.3); |
|
} |
|
|
|
.control-group input::placeholder { |
|
color: rgba(255, 255, 255, 0.6); |
|
} |
|
|
|
.slider-container { |
|
display: flex; |
|
align-items: center; |
|
gap: 10px; |
|
} |
|
|
|
.slider-container input[type="range"] { |
|
flex: 1; |
|
} |
|
|
|
.slider-value { |
|
background: rgba(0, 0, 0, 0.3); |
|
padding: 4px 8px; |
|
border-radius: 4px; |
|
font-size: 0.8rem; |
|
min-width: 40px; |
|
text-align: center; |
|
} |
|
|
|
.btn { |
|
width: 100%; |
|
padding: 12px; |
|
border: none; |
|
border-radius: 8px; |
|
font-size: 16px; |
|
font-weight: 600; |
|
cursor: pointer; |
|
transition: all 0.3s ease; |
|
margin-bottom: 10px; |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
.btn::before { |
|
content: ''; |
|
position: absolute; |
|
top: 0; |
|
left: -100%; |
|
width: 100%; |
|
height: 100%; |
|
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent); |
|
transition: left 0.5s; |
|
} |
|
|
|
.btn:hover::before { |
|
left: 100%; |
|
} |
|
|
|
.btn-primary { |
|
background: linear-gradient(45deg, #4CAF50, #45a049); |
|
color: white; |
|
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.3); |
|
} |
|
|
|
.btn-secondary { |
|
background: linear-gradient(45deg, #2196F3, #1976D2); |
|
color: white; |
|
box-shadow: 0 4px 15px rgba(33, 150, 243, 0.3); |
|
} |
|
|
|
.btn-danger { |
|
background: linear-gradient(45deg, #f44336, #d32f2f); |
|
color: white; |
|
box-shadow: 0 4px 15px rgba(244, 67, 54, 0.3); |
|
} |
|
|
|
.btn:hover { |
|
transform: translateY(-2px); |
|
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); |
|
} |
|
|
|
.btn:active { |
|
transform: translateY(0); |
|
} |
|
|
|
.btn:disabled { |
|
opacity: 0.6; |
|
cursor: not-allowed; |
|
transform: none; |
|
} |
|
|
|
.stats { |
|
background: rgba(0, 0, 0, 0.3); |
|
padding: 15px; |
|
border-radius: 8px; |
|
margin-top: 15px; |
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
} |
|
|
|
.stats h4 { |
|
margin-bottom: 12px; |
|
color: #fff; |
|
font-size: 1.1rem; |
|
} |
|
|
|
.stat-item { |
|
display: flex; |
|
justify-content: space-between; |
|
margin-bottom: 8px; |
|
font-size: 14px; |
|
padding: 4px 0; |
|
} |
|
|
|
.stat-label { |
|
color: #ccc; |
|
} |
|
|
|
.stat-value { |
|
color: #4CAF50; |
|
font-weight: 600; |
|
font-family: monospace; |
|
} |
|
|
|
.ai-info { |
|
background: rgba(0, 0, 0, 0.3); |
|
padding: 15px; |
|
border-radius: 8px; |
|
margin-top: 15px; |
|
font-size: 13px; |
|
line-height: 1.5; |
|
border: 1px solid rgba(255, 255, 255, 0.1); |
|
} |
|
|
|
.ai-info h4 { |
|
margin-bottom: 10px; |
|
color: #fff; |
|
} |
|
|
|
.ai-info ul { |
|
margin-left: 15px; |
|
margin-top: 8px; |
|
} |
|
|
|
.ai-info li { |
|
margin-bottom: 4px; |
|
color: #e0e0e0; |
|
} |
|
|
|
.current-decision { |
|
background: rgba(76, 175, 80, 0.2); |
|
padding: 8px; |
|
border-radius: 4px; |
|
margin: 10px 0; |
|
font-family: monospace; |
|
font-size: 12px; |
|
border-left: 3px solid #4CAF50; |
|
} |
|
|
|
.legend { |
|
display: flex; |
|
flex-wrap: wrap; |
|
gap: 15px; |
|
margin-top: 15px; |
|
padding: 10px; |
|
background: rgba(0, 0, 0, 0.2); |
|
border-radius: 8px; |
|
} |
|
|
|
.legend-item { |
|
display: flex; |
|
align-items: center; |
|
gap: 8px; |
|
font-size: 12px; |
|
} |
|
|
|
.legend-color { |
|
width: 16px; |
|
height: 16px; |
|
border-radius: 50%; |
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); |
|
} |
|
|
|
.footer { |
|
background: rgba(0, 0, 0, 0.3); |
|
padding: 15px; |
|
text-align: center; |
|
backdrop-filter: blur(10px); |
|
border-top: 1px solid rgba(255, 255, 255, 0.1); |
|
font-size: 0.9rem; |
|
color: rgba(255, 255, 255, 0.8); |
|
} |
|
|
|
.footer a { |
|
color: #4CAF50; |
|
text-decoration: none; |
|
} |
|
|
|
.footer a:hover { |
|
text-decoration: underline; |
|
} |
|
|
|
|
|
@media (max-width: 768px) { |
|
.main-container { |
|
flex-direction: column; |
|
padding: 10px; |
|
} |
|
|
|
.controls-panel { |
|
width: 100%; |
|
} |
|
|
|
.canvas-container { |
|
height: 400px; |
|
} |
|
|
|
.header { |
|
padding: 15px; |
|
} |
|
|
|
.hf-badge { |
|
position: static; |
|
margin-top: 10px; |
|
display: inline-block; |
|
} |
|
} |
|
|
|
|
|
.loading { |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
right: 0; |
|
bottom: 0; |
|
background: rgba(26, 26, 46, 0.9); |
|
color: white; |
|
font-size: 18px; |
|
} |
|
|
|
.loading::after { |
|
content: ''; |
|
width: 40px; |
|
height: 40px; |
|
border: 4px solid rgba(255, 255, 255, 0.3); |
|
border-top: 4px solid #4CAF50; |
|
border-radius: 50%; |
|
animation: spin 1s linear infinite; |
|
margin-left: 15px; |
|
} |
|
|
|
@keyframes spin { |
|
0% { transform: rotate(0deg); } |
|
100% { transform: rotate(360deg); } |
|
} |
|
|
|
|
|
.pulse { |
|
animation: pulse 2s infinite; |
|
} |
|
|
|
@keyframes pulse { |
|
0% { opacity: 1; } |
|
50% { opacity: 0.5; } |
|
100% { opacity: 1; } |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div class="header"> |
|
<div class="hf-badge">🤗 Hugging Face Space</div> |
|
<h1>🚗 Embodied AI Vehicle Simulator</h1> |
|
<p>Interactive autonomous vehicle navigation with real-time AI decision making</p> |
|
</div> |
|
|
|
<div class="main-container"> |
|
<div class="simulation-area"> |
|
<div class="canvas-container"> |
|
<canvas id="simulationCanvas"></canvas> |
|
<div class="loading" id="loadingIndicator"> |
|
Initializing AI System... |
|
</div> |
|
</div> |
|
|
|
<div class="legend"> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background: #4CAF50;"></div> |
|
<span>Autonomous Vehicle</span> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background: #f44336;"></div> |
|
<span>Obstacles</span> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background: #2196F3;"></div> |
|
<span>Target Destinations</span> |
|
</div> |
|
<div class="legend-item"> |
|
<div class="legend-color" style="background: #FFC107;"></div> |
|
<span>LiDAR Sensors</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="controls-panel"> |
|
<div class="control-section"> |
|
<h3>🎮 Simulation Controls</h3> |
|
<button class="btn btn-primary" id="startBtn">▶️ Start Simulation</button> |
|
<button class="btn btn-secondary" id="pauseBtn" disabled>⏸️ Pause</button> |
|
<button class="btn btn-danger" id="resetBtn">🔄 Reset</button> |
|
</div> |
|
|
|
<div class="control-section"> |
|
<h3>⚙️ AI Parameters</h3> |
|
<div class="control-group"> |
|
<label>Vehicle Speed</label> |
|
<div class="slider-container"> |
|
<input type="range" id="speedSlider" min="0" max="5" value="2" step="0.5"> |
|
<span class="slider-value" id="speedValue">2.0</span> |
|
</div> |
|
</div> |
|
<div class="control-group"> |
|
<label>Sensor Range</label> |
|
<div class="slider-container"> |
|
<input type="range" id="sensorRange" min="50" max="200" value="100" step="10"> |
|
<span class="slider-value" id="sensorValue">100</span> |
|
</div> |
|
</div> |
|
<div class="control-group"> |
|
<label>AI Behavior</label> |
|
<select id="behaviorSelect"> |
|
<option value="cautious">🐌 Cautious</option> |
|
<option value="normal" selected>🚗 Normal</option> |
|
<option value="aggressive">🏎️ Aggressive</option> |
|
</select> |
|
</div> |
|
</div> |
|
|
|
<div class="control-section"> |
|
<h3>🏭 Environment</h3> |
|
<button class="btn btn-secondary" id="addObstacleBtn">➕ Add Obstacle</button> |
|
<button class="btn btn-secondary" id="addTargetBtn">🎯 Add Target</button> |
|
<button class="btn btn-danger" id="clearBtn">🗑️ Clear All</button> |
|
</div> |
|
|
|
<div class="stats"> |
|
<h4>📊 Performance Metrics</h4> |
|
<div class="stat-item"> |
|
<span class="stat-label">Distance Traveled:</span> |
|
<span class="stat-value" id="distanceTraveled">0m</span> |
|
</div> |
|
<div class="stat-item"> |
|
<span class="stat-label">Targets Reached:</span> |
|
<span class="stat-value" id="targetsReached">0</span> |
|
</div> |
|
<div class="stat-item"> |
|
<span class="stat-label">Obstacles Avoided:</span> |
|
<span class="stat-value" id="obstaclesAvoided">0</span> |
|
</div> |
|
<div class="stat-item"> |
|
<span class="stat-label">AI Decisions/sec:</span> |
|
<span class="stat-value pulse" id="decisionsPerSec">0</span> |
|
</div> |
|
</div> |
|
|
|
<div class="ai-info"> |
|
<h4>🧠 AI Decision Engine</h4> |
|
<div class="current-decision" id="currentDecision">System initializing...</div> |
|
|
|
<p><strong>Embodied AI Features:</strong></p> |
|
<ul> |
|
<li>Real-time sensor processing</li> |
|
<li>Dynamic path planning</li> |
|
<li>Multi-objective optimization</li> |
|
<li>Behavioral adaptation</li> |
|
<li>Environmental coupling</li> |
|
</ul> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="footer"> |
|
<p> |
|
Built with ❤️ for the AI community • |
|
<a href="https://huggingface.co/spaces" target="_blank">Hugging Face Spaces</a> • |
|
Click to add targets, Shift+Click for obstacles |
|
</p> |
|
</div> |
|
|
|
<script> |
|
|
|
const canvas = document.getElementById('simulationCanvas'); |
|
const ctx = canvas.getContext('2d'); |
|
const loadingIndicator = document.getElementById('loadingIndicator'); |
|
|
|
|
|
let devicePixelRatio = window.devicePixelRatio || 1; |
|
|
|
function resizeCanvas() { |
|
const container = canvas.parentElement; |
|
const rect = container.getBoundingClientRect(); |
|
|
|
canvas.width = rect.width * devicePixelRatio; |
|
canvas.height = rect.height * devicePixelRatio; |
|
canvas.style.width = rect.width + 'px'; |
|
canvas.style.height = rect.height + 'px'; |
|
|
|
ctx.scale(devicePixelRatio, devicePixelRatio); |
|
} |
|
|
|
resizeCanvas(); |
|
window.addEventListener('resize', resizeCanvas); |
|
|
|
|
|
let isRunning = false; |
|
let isPaused = false; |
|
let animationId = null; |
|
let lastTime = 0; |
|
let decisionCount = 0; |
|
let lastDecisionTime = 0; |
|
let frameCount = 0; |
|
|
|
|
|
class AutonomousVehicle { |
|
constructor(x, y) { |
|
this.x = x; |
|
this.y = y; |
|
this.vx = 0; |
|
this.vy = 0; |
|
this.angle = 0; |
|
this.speed = 2; |
|
this.maxSpeed = 5; |
|
this.size = 15; |
|
this.sensorRange = 100; |
|
this.sensors = []; |
|
this.path = []; |
|
this.currentTarget = null; |
|
this.distanceTraveled = 0; |
|
this.lastX = x; |
|
this.lastY = y; |
|
this.behavior = 'normal'; |
|
this.avoidanceVector = { x: 0, y: 0 }; |
|
this.stuckCounter = 0; |
|
this.lastPosition = { x: x, y: y }; |
|
this.wanderAngle = 0; |
|
} |
|
|
|
updateSensors() { |
|
this.sensors = []; |
|
const numSensors = 12; |
|
|
|
for (let i = 0; i < numSensors; i++) { |
|
const angle = (i / numSensors) * 2 * Math.PI; |
|
const endX = this.x + Math.cos(angle) * this.sensorRange; |
|
const endY = this.y + Math.sin(angle) * this.sensorRange; |
|
|
|
let distance = this.sensorRange; |
|
let hitObstacle = false; |
|
|
|
|
|
for (let obstacle of obstacles) { |
|
const dx = obstacle.x - this.x; |
|
const dy = obstacle.y - this.y; |
|
const distToObstacle = Math.sqrt(dx * dx + dy * dy); |
|
|
|
if (distToObstacle > 0) { |
|
const angleToObstacle = Math.atan2(dy, dx); |
|
let angleDiff = Math.abs(angle - angleToObstacle); |
|
if (angleDiff > Math.PI) angleDiff = 2 * Math.PI - angleDiff; |
|
|
|
const detectionCone = 0.3; |
|
if (angleDiff < detectionCone) { |
|
const actualDistance = Math.max(0, distToObstacle - obstacle.size - 5); |
|
if (actualDistance < distance) { |
|
distance = actualDistance; |
|
hitObstacle = true; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
const boundaryDistance = this.checkBoundaryDistance(angle); |
|
if (boundaryDistance < distance) { |
|
distance = boundaryDistance; |
|
} |
|
|
|
this.sensors.push({ |
|
angle: angle, |
|
distance: distance, |
|
x: this.x + Math.cos(angle) * distance, |
|
y: this.y + Math.sin(angle) * distance, |
|
hitObstacle: hitObstacle |
|
}); |
|
} |
|
} |
|
|
|
checkBoundaryDistance(angle) { |
|
const canvasWidth = canvas.width / devicePixelRatio; |
|
const canvasHeight = canvas.height / devicePixelRatio; |
|
|
|
const cos = Math.cos(angle); |
|
const sin = Math.sin(angle); |
|
|
|
let distance = this.sensorRange; |
|
|
|
if (cos > 0) { |
|
distance = Math.min(distance, (canvasWidth - this.x) / cos); |
|
} else if (cos < 0) { |
|
distance = Math.min(distance, -this.x / cos); |
|
} |
|
|
|
if (sin > 0) { |
|
distance = Math.min(distance, (canvasHeight - this.y) / sin); |
|
} else if (sin < 0) { |
|
distance = Math.min(distance, -this.y / sin); |
|
} |
|
|
|
return Math.max(0, distance - 10); |
|
} |
|
} |
|
</script> |