3d-rockets / index.html
LukasBe's picture
Add 2 files
54794e6 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Rocket Launcher</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.js"></script>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Arial', sans-serif;
}
canvas {
display: block;
}
#ui {
position: absolute;
top: 0;
left: 0;
width: 100%;
padding: 20px;
color: white;
text-align: center;
pointer-events: none;
z-index: 100;
}
#crosshair {
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
transform: translate(-50%, -50%);
pointer-events: none;
z-index: 101;
}
#crosshair::before, #crosshair::after {
content: '';
position: absolute;
background: rgba(255, 255, 255, 0.8);
}
#crosshair::before {
width: 2px;
height: 20px;
left: 9px;
top: 0;
}
#crosshair::after {
width: 20px;
height: 2px;
left: 0;
top: 9px;
}
#explosion {
position: absolute;
top: 50%;
left: 50%;
width: 200px;
height: 200px;
transform: translate(-50%, -50%);
border-radius: 50%;
background: radial-gradient(circle, rgba(255,100,0,0.8) 0%, rgba(255,50,0,0) 70%);
opacity: 0;
pointer-events: none;
z-index: 99;
transition: opacity 0.5s, transform 0.5s;
}
.button {
pointer-events: auto;
background: rgba(255, 255, 255, 0.2);
border: 1px solid rgba(255, 255, 255, 0.4);
color: white;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.button:hover {
background: rgba(255, 255, 255, 0.4);
}
</style>
</head>
<body>
<div id="ui">
<h1 class="text-3xl font-bold mb-2">3D Rocket Launcher</h1>
<p class="mb-4">Click anywhere to launch rockets!</p>
<div class="flex justify-center gap-4">
<div class="button" id="fireRocket">Fire Rocket</div>
<div class="button" id="autoFire">Auto Fire</div>
</div>
<div class="mt-4">
<span id="rocketCount">0</span> Rockets Active
</div>
</div>
<div id="crosshair"></div>
<div id="explosion"></div>
<script>
// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111122);
// Camera setup
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 10;
camera.position.y = 5;
camera.lookAt(0, 0, 0);
// Renderer setup
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
document.body.appendChild(renderer.domElement);
// Controls
const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Lights
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 1);
scene.add(directionalLight);
// Rocket materials
const rocketBodyMaterial = new THREE.MeshPhongMaterial({
color: 0xff3333,
shininess: 100,
specular: 0x111111
});
const rocketNoseMaterial = new THREE.MeshPhongMaterial({
color: 0xffffff,
shininess: 100,
specular: 0x111111
});
const rocketFinMaterial = new THREE.MeshPhongMaterial({
color: 0x3333ff,
shininess: 50,
specular: 0x111111
});
// Create a procedural rocket model
function createRocket() {
const rocketGroup = new THREE.Group();
// Rocket body (cylinder with more segments for better quality)
const bodyGeometry = new THREE.CylinderGeometry(0.15, 0.15, 1, 32, 32, false);
const body = new THREE.Mesh(bodyGeometry, rocketBodyMaterial);
body.position.y = 0.5;
body.castShadow = true;
rocketGroup.add(body);
// Rocket nose (cone with more segments)
const noseGeometry = new THREE.ConeGeometry(0.15, 0.3, 32, 1, true);
const nose = new THREE.Mesh(noseGeometry, rocketNoseMaterial);
nose.position.y = 1.15;
nose.castShadow = true;
rocketGroup.add(nose);
// Rocket fins (4 fins for better stability)
const finGeometry = new THREE.BoxGeometry(0.3, 0.05, 0.15);
for (let i = 0; i < 4; i++) {
const fin = new THREE.Mesh(finGeometry, rocketFinMaterial);
fin.position.y = 0.25;
fin.position.z = 0.075;
fin.rotation.x = Math.PI / 2;
fin.rotation.y = (i * Math.PI * 2 / 4);
fin.position.x = Math.sin(fin.rotation.y) * 0.25;
fin.position.z = Math.cos(fin.rotation.y) * 0.25;
fin.castShadow = true;
rocketGroup.add(fin);
}
// Rocket engine (small cylinder with more detail)
const engineGeometry = new THREE.CylinderGeometry(0.1, 0.1, 0.15, 32);
const engine = new THREE.Mesh(engineGeometry, new THREE.MeshPhongMaterial({
color: 0x222222,
shininess: 30
}));
engine.position.y = 0;
engine.castShadow = true;
rocketGroup.add(engine);
// Engine nozzle (cone)
const nozzleGeometry = new THREE.ConeGeometry(0.1, 0.1, 32);
const nozzle = new THREE.Mesh(nozzleGeometry, new THREE.MeshPhongMaterial({
color: 0x444444,
shininess: 50
}));
nozzle.position.y = -0.05;
nozzle.rotation.x = Math.PI;
nozzle.castShadow = true;
rocketGroup.add(nozzle);
// Add some details (rings around the body)
const ringGeometry = new THREE.TorusGeometry(0.15, 0.01, 16, 32);
const ringMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff });
const ring1 = new THREE.Mesh(ringGeometry, ringMaterial);
ring1.position.y = 0.8;
ring1.rotation.x = Math.PI / 2;
rocketGroup.add(ring1);
const ring2 = new THREE.Mesh(ringGeometry, ringMaterial);
ring2.position.y = 0.2;
ring2.rotation.x = Math.PI / 2;
rocketGroup.add(ring2);
return rocketGroup;
}
// Particle systems
function createFireParticles() {
const particleCount = 150;
const particles = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
const sizes = new Float32Array(particleCount);
for (let i = 0; i < particleCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 0.15;
positions[i * 3 + 1] = Math.random() * -0.5;
positions[i * 3 + 2] = (Math.random() - 0.5) * 0.15;
// Fire colors (red, orange, yellow)
const color = new THREE.Color(
0.9 + Math.random() * 0.1,
0.3 + Math.random() * 0.3,
Math.random() * 0.2
);
colors[i * 3] = color.r;
colors[i * 3 + 1] = color.g;
colors[i * 3 + 2] = color.b;
sizes[i] = 0.1 + Math.random() * 0.2;
}
particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
particles.setAttribute('color', new THREE.BufferAttribute(colors, 3));
particles.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
const particleMaterial = new THREE.PointsMaterial({
size: 0.1,
vertexColors: true,
transparent: true,
opacity: 0.8,
blending: THREE.AdditiveBlending
});
const particleSystem = new THREE.Points(particles, particleMaterial);
return particleSystem;
}
function createSmokeParticles() {
const particleCount = 80;
const particles = new THREE.BufferGeometry();
const positions = new Float32Array(particleCount * 3);
const colors = new Float32Array(particleCount * 3);
const sizes = new Float32Array(particleCount);
for (let i = 0; i < particleCount; i++) {
positions[i * 3] = (Math.random() - 0.5) * 0.3;
positions[i * 3 + 1] = Math.random() * -1;
positions[i * 3 + 2] = (Math.random() - 0.5) * 0.3;
// Smoke colors (gray to white)
const gray = 0.5 + Math.random() * 0.5;
const color = new THREE.Color(gray, gray, gray);
colors[i * 3] = color.r;
colors[i * 3 + 1] = color.g;
colors[i * 3 + 2] = color.b;
sizes[i] = 0.2 + Math.random() * 0.3;
}
particles.setAttribute('position', new THREE.BufferAttribute(positions, 3));
particles.setAttribute('color', new THREE.BufferAttribute(colors, 3));
particles.setAttribute('size', new THREE.BufferAttribute(sizes, 1));
const particleMaterial = new THREE.PointsMaterial({
size: 0.2,
vertexColors: true,
transparent: true,
opacity: 0.6,
blending: THREE.NormalBlending
});
const particleSystem = new THREE.Points(particles, particleMaterial);
return particleSystem;
}
// Rocket class
class Rocket {
constructor(startPosition) {
this.group = new THREE.Group();
this.rocket = createRocket();
this.fireParticles = createFireParticles();
this.smokeParticles = createSmokeParticles();
this.group.add(this.rocket);
this.group.add(this.fireParticles);
this.group.add(this.smokeParticles);
this.group.position.copy(startPosition);
// Create a random bezier curve back to center
const endPoint = new THREE.Vector3(0, 0, 0);
const control1 = new THREE.Vector3(
(Math.random() - 0.5) * 10,
Math.random() * 5,
(Math.random() - 0.5) * 10
);
const control2 = new THREE.Vector3(
(Math.random() - 0.5) * 10,
Math.random() * 5,
(Math.random() - 0.5) * 10
);
this.curve = new THREE.CubicBezierCurve3(
startPosition,
control1,
control2,
endPoint
);
this.progress = 0;
this.speed = 0.002 + Math.random() * 0.003;
this.alive = true;
this.exploded = false;
// Store previous position for orientation calculation
this.prevPosition = startPosition.clone();
scene.add(this.group);
activeRockets.push(this);
updateRocketCount();
}
update() {
if (!this.alive) return;
this.progress += this.speed;
if (this.progress >= 1) {
this.explode();
return;
}
// Move along curve
const point = this.curve.getPoint(this.progress);
this.group.position.copy(point);
// Calculate direction vector
const direction = new THREE.Vector3().subVectors(point, this.prevPosition).normalize();
// Only update orientation if we have significant movement
if (direction.length() > 0.001) {
// Create a quaternion that orients the rocket along the direction vector
const targetQuaternion = new THREE.Quaternion();
const up = new THREE.Vector3(0, 1, 0);
// Create a matrix that looks in the direction we're moving
const rotationMatrix = new THREE.Matrix4();
rotationMatrix.lookAt(new THREE.Vector3(0, 0, 0), direction, up);
// Extract quaternion from matrix
targetQuaternion.setFromRotationMatrix(rotationMatrix);
// Apply rotation to the rocket (not the whole group)
this.rocket.quaternion.copy(targetQuaternion);
// Store current position for next frame
this.prevPosition.copy(point);
}
// Update particles
this.updateParticles();
}
updateParticles() {
// Fire particles
const firePositions = this.fireParticles.geometry.attributes.position.array;
for (let i = 0; i < firePositions.length; i += 3) {
firePositions[i + 1] -= 0.01;
if (firePositions[i + 1] < -0.5) {
firePositions[i] = (Math.random() - 0.5) * 0.15;
firePositions[i + 1] = 0;
firePositions[i + 2] = (Math.random() - 0.5) * 0.15;
}
}
this.fireParticles.geometry.attributes.position.needsUpdate = true;
// Smoke particles
const smokePositions = this.smokeParticles.geometry.attributes.position.array;
for (let i = 0; i < smokePositions.length; i += 3) {
smokePositions[i] += (Math.random() - 0.5) * 0.01;
smokePositions[i + 1] += 0.01;
smokePositions[i + 2] += (Math.random() - 0.5) * 0.01;
if (smokePositions[i + 1] > 0) {
smokePositions[i] = (Math.random() - 0.5) * 0.3;
smokePositions[i + 1] = -1 + Math.random() * 0.5;
smokePositions[i + 2] = (Math.random() - 0.5) * 0.3;
}
}
this.smokeParticles.geometry.attributes.position.needsUpdate = true;
}
explode() {
this.alive = false;
this.exploded = true;
// Show explosion effect
const explosion = document.getElementById('explosion');
explosion.style.opacity = '0.8';
explosion.style.transform = 'translate(-50%, -50%) scale(1)';
setTimeout(() => {
explosion.style.opacity = '0';
explosion.style.transform = 'translate(-50%, -50%) scale(0.5)';
}, 500);
// Remove from scene after a delay
setTimeout(() => {
scene.remove(this.group);
const index = activeRockets.indexOf(this);
if (index > -1) {
activeRockets.splice(index, 1);
}
updateRocketCount();
}, 1000);
}
}
// Active rockets array
let activeRockets = [];
// UI functions
function updateRocketCount() {
document.getElementById('rocketCount').textContent = activeRockets.length;
}
// Mouse position to 3D coordinates
function getMouseWorldPosition(event, targetZ = 0) {
const mouse = new THREE.Vector2();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
const raycaster = new THREE.Raycaster();
raycaster.setFromCamera(mouse, camera);
const plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), targetZ);
const intersection = new THREE.Vector3();
raycaster.ray.intersectPlane(plane, intersection);
return intersection;
}
// Event listeners
document.addEventListener('click', (event) => {
if (event.target.closest('.button')) return;
const mousePos = getMouseWorldPosition(event, 0);
new Rocket(mousePos);
});
document.getElementById('fireRocket').addEventListener('click', () => {
const x = (Math.random() - 0.5) * 10;
const y = (Math.random() - 0.5) * 10;
const z = (Math.random() - 0.5) * 10;
new Rocket(new THREE.Vector3(x, y, z));
});
let autoFireInterval = null;
document.getElementById('autoFire').addEventListener('click', function() {
if (autoFireInterval) {
clearInterval(autoFireInterval);
autoFireInterval = null;
this.textContent = 'Auto Fire';
} else {
autoFireInterval = setInterval(() => {
const x = (Math.random() - 0.5) * 10;
const y = (Math.random() - 0.5) * 10;
const z = (Math.random() - 0.5) * 10;
new Rocket(new THREE.Vector3(x, y, z));
}, 500);
this.textContent = 'Stop Auto Fire';
}
});
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Animation loop
function animate() {
requestAnimationFrame(animate);
controls.update();
// Update all rockets
for (let i = 0; i < activeRockets.length; i++) {
activeRockets[i].update();
}
renderer.render(scene, camera);
}
animate();
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=LukasBe/3d-rockets" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>