|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>3D Interactive Cat</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.min.js"></script> |
|
<script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/GLTFLoader.min.js"></script> |
|
<style> |
|
body { |
|
margin: 0; |
|
overflow: hidden; |
|
touch-action: none; |
|
} |
|
#container { |
|
position: relative; |
|
width: 100vw; |
|
height: 100vh; |
|
} |
|
#loading { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background: rgba(0,0,0,0.7); |
|
display: flex; |
|
flex-direction: column; |
|
justify-content: center; |
|
align-items: center; |
|
color: white; |
|
font-family: Arial, sans-serif; |
|
z-index: 100; |
|
} |
|
.spinner { |
|
width: 50px; |
|
height: 50px; |
|
border: 5px solid rgba(255,255,255,0.3); |
|
border-radius: 50%; |
|
border-top-color: #fff; |
|
animation: spin 1s ease-in-out infinite; |
|
margin-bottom: 20px; |
|
} |
|
@keyframes spin { |
|
to { transform: rotate(360deg); } |
|
} |
|
#instructions { |
|
position: absolute; |
|
bottom: 20px; |
|
width: 100%; |
|
text-align: center; |
|
color: white; |
|
font-family: Arial, sans-serif; |
|
background: rgba(0,0,0,0.5); |
|
padding: 10px; |
|
box-sizing: border-box; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-900"> |
|
<div id="container"> |
|
<div id="loading"> |
|
<div class="spinner"></div> |
|
<h1 class="text-2xl font-bold mb-2">Loading 3D Cat...</h1> |
|
<p class="text-gray-300">Please wait while we prepare your interactive feline friend</p> |
|
</div> |
|
<div id="instructions"> |
|
<p class="text-lg">Touch and drag to rotate the cat | Pinch to zoom</p> |
|
<p class="text-sm text-gray-300 hidden md:block">On desktop: Click and drag to rotate | Scroll to zoom</p> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
const container = document.getElementById('container'); |
|
const loadingScreen = document.getElementById('loading'); |
|
|
|
const scene = new THREE.Scene(); |
|
scene.background = new THREE.Color(0x333333); |
|
|
|
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); |
|
camera.position.z = 5; |
|
|
|
const renderer = new THREE.WebGLRenderer({ antialias: true }); |
|
renderer.setPixelRatio(window.devicePixelRatio); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
renderer.shadowMap.enabled = true; |
|
container.appendChild(renderer.domElement); |
|
|
|
|
|
const ambientLight = new THREE.AmbientLight(0x404040); |
|
scene.add(ambientLight); |
|
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 1); |
|
directionalLight.position.set(1, 1, 1); |
|
directionalLight.castShadow = true; |
|
scene.add(directionalLight); |
|
|
|
const directionalLight2 = new THREE.DirectionalLight(0xffffff, 0.5); |
|
directionalLight2.position.set(-1, -1, -1); |
|
scene.add(directionalLight2); |
|
|
|
|
|
const controls = new THREE.OrbitControls(camera, renderer.domElement); |
|
controls.enableDamping = true; |
|
controls.dampingFactor = 0.05; |
|
controls.screenSpacePanning = false; |
|
controls.maxPolarAngle = Math.PI; |
|
controls.minPolarAngle = 0; |
|
controls.minDistance = 2; |
|
controls.maxDistance = 10; |
|
|
|
|
|
function createCat() { |
|
const group = new THREE.Group(); |
|
|
|
|
|
const bodyGeometry = new THREE.SphereGeometry(1, 32, 32); |
|
bodyGeometry.scale(1.5, 0.8, 0.6); |
|
const bodyMaterial = new THREE.MeshStandardMaterial({ |
|
color: 0xaaaaaa, |
|
roughness: 0.7, |
|
metalness: 0.1 |
|
}); |
|
const body = new THREE.Mesh(bodyGeometry, bodyMaterial); |
|
body.castShadow = true; |
|
body.receiveShadow = true; |
|
group.add(body); |
|
|
|
|
|
const headGeometry = new THREE.SphereGeometry(0.6, 32, 32); |
|
const headMaterial = new THREE.MeshStandardMaterial({ |
|
color: 0xaaaaaa, |
|
roughness: 0.7, |
|
metalness: 0.1 |
|
}); |
|
const head = new THREE.Mesh(headGeometry, headMaterial); |
|
head.position.set(1.2, 0.2, 0); |
|
head.castShadow = true; |
|
group.add(head); |
|
|
|
|
|
const earGeometry = new THREE.ConeGeometry(0.3, 0.5, 32); |
|
earGeometry.rotateX(Math.PI / 2); |
|
|
|
const leftEar = new THREE.Mesh(earGeometry, headMaterial); |
|
leftEar.position.set(1.5, 0.5, 0.3); |
|
leftEar.rotation.z = -0.5; |
|
group.add(leftEar); |
|
|
|
const rightEar = new THREE.Mesh(earGeometry, headMaterial); |
|
rightEar.position.set(1.5, 0.5, -0.3); |
|
rightEar.rotation.z = 0.5; |
|
group.add(rightEar); |
|
|
|
|
|
const eyeGeometry = new THREE.SphereGeometry(0.1, 16, 16); |
|
const eyeMaterial = new THREE.MeshStandardMaterial({ color: 0x111111 }); |
|
|
|
const leftEye = new THREE.Mesh(eyeGeometry, eyeMaterial); |
|
leftEye.position.set(1.6, 0.2, 0.2); |
|
group.add(leftEye); |
|
|
|
const rightEye = new THREE.Mesh(eyeGeometry, eyeMaterial); |
|
rightEye.position.set(1.6, 0.2, -0.2); |
|
group.add(rightEye); |
|
|
|
|
|
const noseGeometry = new THREE.ConeGeometry(0.08, 0.2, 32); |
|
noseGeometry.rotateX(Math.PI / 2); |
|
const noseMaterial = new THREE.MeshStandardMaterial({ color: 0xff9999 }); |
|
const nose = new THREE.Mesh(noseGeometry, noseMaterial); |
|
nose.position.set(1.7, 0.1, 0); |
|
group.add(nose); |
|
|
|
|
|
const whiskerMaterial = new THREE.LineBasicMaterial({ color: 0xffffff }); |
|
|
|
|
|
const leftWhisker1 = new THREE.Line( |
|
new THREE.BufferGeometry().setFromPoints([ |
|
new THREE.Vector3(1.6, 0.1, 0.2), |
|
new THREE.Vector3(1.8, 0.15, 0.4) |
|
]), |
|
whiskerMaterial |
|
); |
|
group.add(leftWhisker1); |
|
|
|
const leftWhisker2 = new THREE.Line( |
|
new THREE.BufferGeometry().setFromPoints([ |
|
new THREE.Vector3(1.6, 0.1, 0.2), |
|
new THREE.Vector3(1.8, 0.1, 0.4) |
|
]), |
|
whiskerMaterial |
|
); |
|
group.add(leftWhisker2); |
|
|
|
const leftWhisker3 = new THREE.Line( |
|
new THREE.BufferGeometry().setFromPoints([ |
|
new THREE.Vector3(1.6, 0.1, 0.2), |
|
new THREE.Vector3(1.8, 0.05, 0.4) |
|
]), |
|
whiskerMaterial |
|
); |
|
group.add(leftWhisker3); |
|
|
|
|
|
const rightWhisker1 = new THREE.Line( |
|
new THREE.BufferGeometry().setFromPoints([ |
|
new THREE.Vector3(1.6, 0.1, -0.2), |
|
new THREE.Vector3(1.8, 0.15, -0.4) |
|
]), |
|
whiskerMaterial |
|
); |
|
group.add(rightWhisker1); |
|
|
|
const rightWhisker2 = new THREE.Line( |
|
new THREE.BufferGeometry().setFromPoints([ |
|
new THREE.Vector3(1.6, 0.1, -0.2), |
|
new THREE.Vector3(1.8, 0.1, -0.4) |
|
]), |
|
whiskerMaterial |
|
); |
|
group.add(rightWhisker2); |
|
|
|
const rightWhisker3 = new THREE.Line( |
|
new THREE.BufferGeometry().setFromPoints([ |
|
new THREE.Vector3(1.6, 0.1, -0.2), |
|
new THREE.Vector3(1.8, 0.05, -0.4) |
|
]), |
|
whiskerMaterial |
|
); |
|
group.add(rightWhisker3); |
|
|
|
|
|
const legGeometry = new THREE.CylinderGeometry(0.15, 0.15, 0.5, 32); |
|
const legMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa }); |
|
|
|
|
|
const frontLeftLeg = new THREE.Mesh(legGeometry, legMaterial); |
|
frontLeftLeg.position.set(0.5, -0.5, 0.4); |
|
frontLeftLeg.rotation.x = Math.PI / 2; |
|
group.add(frontLeftLeg); |
|
|
|
const frontRightLeg = new THREE.Mesh(legGeometry, legMaterial); |
|
frontRightLeg.position.set(0.5, -0.5, -0.4); |
|
frontRightLeg.rotation.x = Math.PI / 2; |
|
group.add(frontRightLeg); |
|
|
|
|
|
const backLeftLeg = new THREE.Mesh(legGeometry, legMaterial); |
|
backLeftLeg.position.set(-0.5, -0.5, 0.4); |
|
backLeftLeg.rotation.x = Math.PI / 2; |
|
group.add(backLeftLeg); |
|
|
|
const backRightLeg = new THREE.Mesh(legGeometry, legMaterial); |
|
backRightLeg.position.set(-0.5, -0.5, -0.4); |
|
backRightLeg.rotation.x = Math.PI / 2; |
|
group.add(backRightLeg); |
|
|
|
|
|
const tailCurve = new THREE.CatmullRomCurve3([ |
|
new THREE.Vector3(-1.2, -0.3, 0), |
|
new THREE.Vector3(-1.8, -0.2, 0), |
|
new THREE.Vector3(-2.2, 0, 0), |
|
new THREE.Vector3(-2.4, 0.3, 0) |
|
]); |
|
|
|
const tailGeometry = new THREE.TubeGeometry(tailCurve, 64, 0.1, 16, false); |
|
const tail = new THREE.Mesh(tailGeometry, bodyMaterial); |
|
group.add(tail); |
|
|
|
return group; |
|
} |
|
|
|
|
|
const cat = createCat(); |
|
scene.add(cat); |
|
|
|
|
|
const floorGeometry = new THREE.PlaneGeometry(20, 20); |
|
const floorMaterial = new THREE.MeshStandardMaterial({ |
|
color: 0x666666, |
|
roughness: 0.8, |
|
metalness: 0.2 |
|
}); |
|
const floor = new THREE.Mesh(floorGeometry, floorMaterial); |
|
floor.rotation.x = -Math.PI / 2; |
|
floor.position.y = -0.8; |
|
floor.receiveShadow = true; |
|
scene.add(floor); |
|
|
|
|
|
setTimeout(() => { |
|
loadingScreen.style.opacity = 0; |
|
setTimeout(() => { |
|
loadingScreen.style.display = 'none'; |
|
}, 500); |
|
}, 1500); |
|
|
|
|
|
window.addEventListener('resize', () => { |
|
camera.aspect = window.innerWidth / window.innerHeight; |
|
camera.updateProjectionMatrix(); |
|
renderer.setSize(window.innerWidth, window.innerHeight); |
|
}); |
|
|
|
|
|
function animate() { |
|
requestAnimationFrame(animate); |
|
controls.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=Broshy/deepsite-3d-cat-touch-controls" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |