|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Metallic Infinity Möbius Strip</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/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script> |
|
|
<style> |
|
|
body { |
|
|
margin: 0; |
|
|
overflow: hidden; |
|
|
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); |
|
|
} |
|
|
#info { |
|
|
position: absolute; |
|
|
bottom: 20px; |
|
|
width: 100%; |
|
|
text-align: center; |
|
|
color: white; |
|
|
font-family: 'Arial', sans-serif; |
|
|
pointer-events: none; |
|
|
text-shadow: 0 0 5px rgba(0,0,0,0.5); |
|
|
} |
|
|
.title { |
|
|
position: absolute; |
|
|
top: 20px; |
|
|
width: 100%; |
|
|
text-align: center; |
|
|
color: white; |
|
|
font-family: 'Arial', sans-serif; |
|
|
font-size: 2.5rem; |
|
|
font-weight: bold; |
|
|
text-shadow: 0 0 15px rgba(255, 215, 0, 0.8); |
|
|
} |
|
|
.controls { |
|
|
position: absolute; |
|
|
top: 80px; |
|
|
right: 20px; |
|
|
background: rgba(0,0,0,0.5); |
|
|
padding: 15px; |
|
|
border-radius: 10px; |
|
|
color: white; |
|
|
font-family: 'Arial', sans-serif; |
|
|
z-index: 100; |
|
|
backdrop-filter: blur(5px); |
|
|
border: 1px solid rgba(255, 215, 0, 0.3); |
|
|
} |
|
|
.control-group { |
|
|
margin-bottom: 12px; |
|
|
} |
|
|
label { |
|
|
display: block; |
|
|
margin-bottom: 5px; |
|
|
color: #ffd700; |
|
|
} |
|
|
input[type="range"] { |
|
|
-webkit-appearance: none; |
|
|
width: 100%; |
|
|
height: 6px; |
|
|
background: rgba(255, 215, 0, 0.2); |
|
|
border-radius: 3px; |
|
|
outline: none; |
|
|
} |
|
|
input[type="range"]::-webkit-slider-thumb { |
|
|
-webkit-appearance: none; |
|
|
width: 16px; |
|
|
height: 16px; |
|
|
background: #ffd700; |
|
|
border-radius: 50%; |
|
|
cursor: pointer; |
|
|
} |
|
|
button { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
<div class="title">Metallic Infinity Möbius Strip</div> |
|
|
<div class="controls"> |
|
|
<div class="control-group"> |
|
|
<label for="rotationSpeed">Rotation Speed</label> |
|
|
<input type="range" id="rotationSpeed" min="0" max="0.02" step="0.001" value="0.005"> |
|
|
</div> |
|
|
<div class="control-group"> |
|
|
<label class="flex items-center"> |
|
|
<input type="checkbox" id="wireframe" class="mr-2 h-4 w-4 accent-yellow-400"> |
|
|
<span>Show Wireframe</span> |
|
|
</label> |
|
|
</div> |
|
|
<div class="flex space-x-2 mt-4"> |
|
|
<button id="pauseBtn" class="bg-yellow-600 hover:bg-yellow-700 text-white px-4 py-2 rounded-lg font-medium shadow-md hover:shadow-lg transform hover:-translate-y-0.5 transition-all"> |
|
|
Pause |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
<div id="info">Drag to rotate | Scroll to zoom</div> |
|
|
<script> |
|
|
|
|
|
const scene = new THREE.Scene(); |
|
|
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
|
|
const renderer = new THREE.WebGLRenderer({ antialias: true }); |
|
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
|
renderer.setPixelRatio(window.devicePixelRatio); |
|
|
renderer.shadowMap.enabled = true; |
|
|
renderer.shadowMap.type = THREE.PCFSoftShadowMap; |
|
|
document.body.appendChild(renderer.domElement); |
|
|
|
|
|
|
|
|
const createInfinityMobiusStrip = (radius = 1, tubeRadius = 0.3, radialSegments = 128, tubularSegments = 128) => { |
|
|
const geometry = new THREE.BufferGeometry(); |
|
|
const vertices = []; |
|
|
const indices = []; |
|
|
const normals = []; |
|
|
const uvs = []; |
|
|
|
|
|
for (let i = 0; i <= radialSegments; i++) { |
|
|
const v = i / radialSegments * Math.PI * 2; |
|
|
|
|
|
for (let j = 0; j <= tubularSegments; j++) { |
|
|
const u = j / tubularSegments * Math.PI * 4; |
|
|
|
|
|
|
|
|
const scale = 1.5; |
|
|
const x = scale * Math.cos(u) / (1 + Math.sin(u)*Math.sin(u)); |
|
|
const y = scale * Math.sin(u) * Math.cos(u) / (1 + Math.sin(u)*Math.sin(u)); |
|
|
|
|
|
|
|
|
const nx = x + tubeRadius * Math.cos(v/2) * Math.cos(u); |
|
|
const ny = y + tubeRadius * Math.cos(v/2) * Math.sin(u); |
|
|
const nz = tubeRadius * Math.sin(v/2); |
|
|
|
|
|
vertices.push(nx, ny, nz); |
|
|
|
|
|
|
|
|
const normal = new THREE.Vector3( |
|
|
Math.cos(u) * Math.cos(v/2), |
|
|
Math.sin(u) * Math.cos(v/2), |
|
|
Math.sin(v/2) |
|
|
).normalize(); |
|
|
normals.push(normal.x, normal.y, normal.z); |
|
|
|
|
|
uvs.push(j / tubularSegments, i / radialSegments); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
for (let i = 0; i < radialSegments; i++) { |
|
|
for (let j = 0; j < tubularSegments; j++) { |
|
|
const a = i * (tubularSegments + 1) + j; |
|
|
const b = a + tubularSegments + 1; |
|
|
const c = a + 1; |
|
|
const d = b + 1; |
|
|
|
|
|
indices.push(a, b, d); |
|
|
indices.push(a, d, c); |
|
|
} |
|
|
} |
|
|
|
|
|
geometry.setIndex(indices); |
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3)); |
|
|
geometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3)); |
|
|
geometry.setAttribute('uv', new THREE.Float32BufferAttribute(uvs, 2)); |
|
|
|
|
|
return geometry; |
|
|
}; |
|
|
|
|
|
|
|
|
const material = new THREE.MeshPhysicalMaterial({ |
|
|
color: 0xffd700, |
|
|
metalness: 1.0, |
|
|
roughness: 0.1, |
|
|
clearcoat: 1.0, |
|
|
clearcoatRoughness: 0.05, |
|
|
ior: 2.5, |
|
|
specularIntensity: 1.5, |
|
|
envMapIntensity: 2.0, |
|
|
emissive: 0xffcc00, |
|
|
emissiveIntensity: 0.2, |
|
|
side: THREE.DoubleSide, |
|
|
}); |
|
|
|
|
|
const wireframeMaterial = new THREE.MeshBasicMaterial({ |
|
|
color: 0xffffff, |
|
|
wireframe: true, |
|
|
transparent: true, |
|
|
opacity: 0.3 |
|
|
}); |
|
|
|
|
|
|
|
|
const mobiusStrip = new THREE.Mesh(createInfinityMobiusStrip(2, 0.4, 128, 128), material); |
|
|
scene.add(mobiusStrip); |
|
|
mobiusStrip.rotation.x = Math.PI / 4; |
|
|
mobiusStrip.rotation.y = Math.PI / 4; |
|
|
|
|
|
|
|
|
const wireframe = new THREE.Mesh(mobiusStrip.geometry, wireframeMaterial); |
|
|
scene.add(wireframe); |
|
|
wireframe.visible = false; |
|
|
|
|
|
|
|
|
const ambientLight = new THREE.AmbientLight(0x404040, 0.3); |
|
|
scene.add(ambientLight); |
|
|
|
|
|
|
|
|
const directionalLight1 = new THREE.DirectionalLight(0xffffff, 0.8); |
|
|
directionalLight1.position.set(2, 2, 2); |
|
|
directionalLight1.castShadow = true; |
|
|
directionalLight1.shadow.mapSize.width = 2048; |
|
|
directionalLight1.shadow.mapSize.height = 2048; |
|
|
scene.add(directionalLight1); |
|
|
|
|
|
|
|
|
const directionalLight2 = new THREE.DirectionalLight(0xff9d00, 0.6); |
|
|
directionalLight2.position.set(-1, -1, -1); |
|
|
scene.add(directionalLight2); |
|
|
|
|
|
|
|
|
const pointLight = new THREE.PointLight(0xffcc00, 2, 15); |
|
|
pointLight.position.set(3, 5, 0); |
|
|
pointLight.castShadow = true; |
|
|
scene.add(pointLight); |
|
|
|
|
|
|
|
|
const pointLight2 = new THREE.PointLight(0xff6600, 1.5, 15); |
|
|
pointLight2.position.set(-3, -5, 0); |
|
|
scene.add(pointLight2); |
|
|
|
|
|
|
|
|
camera.position.set(0, 3, 10); |
|
|
camera.lookAt(0, 0, 0); |
|
|
|
|
|
|
|
|
const controls = new THREE.OrbitControls(camera, renderer.domElement); |
|
|
controls.enableDamping = true; |
|
|
controls.dampingFactor = 0.05; |
|
|
controls.screenSpacePanning = false; |
|
|
controls.minDistance = 5; |
|
|
controls.maxDistance = 20; |
|
|
controls.maxPolarAngle = Math.PI * 0.9; |
|
|
controls.minPolarAngle = Math.PI * 0.1; |
|
|
|
|
|
|
|
|
const cubeTextureLoader = new THREE.CubeTextureLoader(); |
|
|
const envMap = cubeTextureLoader.load([ |
|
|
'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_px.jpg', |
|
|
'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_nx.jpg', |
|
|
'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_py.jpg', |
|
|
'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_ny.jpg', |
|
|
'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_pz.jpg', |
|
|
'https://threejs.org/examples/textures/cube/MilkyWay/dark-s_nz.jpg' |
|
|
]); |
|
|
scene.background = envMap; |
|
|
scene.environment = envMap; |
|
|
material.envMap = envMap; |
|
|
|
|
|
|
|
|
const rotationSpeedInput = document.getElementById('rotationSpeed'); |
|
|
const wireframeCheckbox = document.getElementById('wireframe'); |
|
|
const pauseBtn = document.getElementById('pauseBtn'); |
|
|
|
|
|
let isPaused = false; |
|
|
let autoRotateSpeed = 0.005; |
|
|
|
|
|
wireframeCheckbox.addEventListener('change', function() { |
|
|
wireframe.visible = this.checked; |
|
|
}); |
|
|
|
|
|
pauseBtn.addEventListener('click', function() { |
|
|
isPaused = !isPaused; |
|
|
pauseBtn.textContent = isPaused ? 'Play' : 'Pause'; |
|
|
pauseBtn.classList.toggle('bg-yellow-700'); |
|
|
pauseBtn.classList.toggle('bg-yellow-600'); |
|
|
}); |
|
|
|
|
|
|
|
|
let time = 0; |
|
|
function animate() { |
|
|
requestAnimationFrame(animate); |
|
|
time += 0.01; |
|
|
|
|
|
if (!isPaused) { |
|
|
|
|
|
mobiusStrip.rotation.x += autoRotateSpeed * 0.8; |
|
|
mobiusStrip.rotation.y += autoRotateSpeed * 0.5; |
|
|
mobiusStrip.rotation.z += autoRotateSpeed * 0.2; |
|
|
|
|
|
|
|
|
pointLight.intensity = 2 + Math.sin(time) * 0.5; |
|
|
pointLight2.intensity = 1.5 + Math.cos(time * 0.8) * 0.3; |
|
|
|
|
|
|
|
|
autoRotateSpeed = parseFloat(rotationSpeedInput.value); |
|
|
|
|
|
wireframe.rotation.copy(mobiusStrip.rotation); |
|
|
} |
|
|
|
|
|
controls.update(); |
|
|
renderer.render(scene, camera); |
|
|
} |
|
|
|
|
|
|
|
|
window.addEventListener('resize', function() { |
|
|
camera.aspect = window.innerWidth / window.innerHeight; |
|
|
camera.updateProjectionMatrix(); |
|
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
|
}); |
|
|
|
|
|
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=etnom/mobius" style="color: #fff;text-decoration: underline;" target="_blank" >🧬 Remix</a></p></body> |
|
|
</html> |