GİRİŞ
Aşağıdaki makale, verdiğiniz “3D Gerçekçi İnteraktif Örümcek” kodunun nasıl çalıştığını adım adım, mimariden kullanıcı etkileşimine kadar ayrıntılı biçimde incelemektedir. Kod, tarayıcıda çalışan eksiksiz bir mini-uygulama üretir: WebGL üzerinde Three.js kullanarak modellendiği için GPU hızlandırmalı gerçek-zamanlı grafikler sunar, dahili bir “fizik mikro-motoru” barındırır ve sezgisel bir kontrol paneliyle parameterik olarak ayarlanabilir.
1. Teknoloji Yığını
| Katman | Kullanılan Teknoloji | Rolü |
| ------------------------------ | ----------------------------- | ---------------------------------------------------------------------------------------- |
| HTML5 | Semantik iskelet | Canvas kaplayıcı, kontrol paneli, yükleme animasyonu, talimat kutusu |
| CSS3 | Değişken tabanlı temalandırma | Karanlık siber-punk arka plan, yarı saydam “glass-morphism” paneller, responsive tasarım |
| JavaScript (ES6 sınıfları) | Uygulama mantığı | Bug3D
sınıfı, fizik & animasyon, olay yönetimi |
| Three.js (r128) | 3D motoru | Sahne, kamera, ışık, gölge, malzeme ve mesh hiyerarşisi |
| WebGL (tarayıcı yerleşik) | Donanım hızlandırma | GPU üstünde gerçek-zamanlı 3D render |
2. Yapısal Bileşenler
2.1 HTML İskeleti
#canvas-container
tam ekran kaplayarak renderer çıktı yüzeyini barındırır.
.control-panel
kayan, minimize edilebilir bir UI dock görevindedir; örümceğin fiziksel/estetik parametrelerini anlık değiştirmenize izin verir.
.instructions
kutusu temel klavye-fare kontrollerini gösterir.
<div class="loading">
sahne inşa edilirken kullanıcıyı bilgilendirir ve tüm engine hazır olduğunda gizlenir.
2.2 CSS Tasarımı
Kök Değişkenler (:root
)
Renk kodları, UI teması ve vurgu renkleri tek merkezden yönetilir.
Glass-morphism
backdrop-filter: blur(10px)
+ yarı saydam rgba() değerleri kontrol paneline “donuk cam” hissi verir; modern UI trendleriyle uyumludur.
Özel Slider & Button Stili
Responsive Breakpoint
768 px altındaki ekranlarda panel boyutu ve talimat kutusu daralır; bu sayede mobil uyum korunur.
3. JavaScript Mimarisi
Kodun kalbi, tek bir ES6 sınıfı: Bug3D
.
classDiagram
class Bug3D {
-scene : THREE.Scene
-camera : THREE.PerspectiveCamera
-renderer : THREE.WebGLRenderer
-bugGroup : THREE.Group
-legs : Array
-settings : Object
-velocity, gravity, jumpForce, friction
+initThreeJS()
+initBug()
+initControls()
+updateBugMovement()
+updateBugAnimation()
+updateCamera()
+animate()
}
3.1 initThreeJS()
Sahne & Arka Plan: Gece mavisi tonlarında tekdüze renk, kontrast vurgularını öne çıkarır.
Kamera: 75° FOV, dinamik takip için konum değerleri updateCamera
ile sürekli güncellenir.
Renderer: antialias:true
+ shadowMap
→ yumuşak kenarlı gölgeler.
Işıklandırma
AmbientLight: genel ortam parıltısı.
DirectionalLight: Güneş ışığına benzer; gölge oluşturur, yoğunluğu panelden değiştirilebilir.
Zemin & GridHelper: 50 × 50 Plane + kılavuz ızgara; örümceğin konumunu görsel olarak sabitler.
3.2 initBug() – Gerçekçi Örümcek Modeli
Gövdeler
- Cephalothorax (baş-gövde), Abdomen (karın): Ölçeklenmiş küre geometrileri, parlak MeshPhong malzemeler.
Dış uzuvlar
Göz Sistemi
2 ana + 6 yan göz; mikro döndürmelerle “av takibi” efekti verir.
Bacak Hiyerarşisi
Her bacak 6-7 segmentli bağımsız grupla inşa edilir (Coxa → Trochanter → Femur → Patella → Tibia → Metatarsus → Tarsus → Pençe).
Çok sayıda THREE.Group
iç içe kullanmak, doğal eklem dönüşlerini kolaylaştırır.
leg.baseRotations
başlangıç açılarını saklar; animasyon boyunca bu rotasyonlar üzerine salınım eklenir (dalga yürüme).
3.3 Fizik Motoru
Basitleştirilmiş bir kinematik güncelleme modeli:
velocity += controls - friction - gravity
position += velocity
- Yerçekimi
this.gravity
(negatif) yalnızca havadayken uygulanır.
- Zıplama
jump()
metodu; isJumping
bayrağı “çifte zıplama”yı engeller.
- Sürtünme yatay eksenlerde enerjiyi azaltır, kaygan zemin izlenimi yaratır.
- Zemin seviyesi
groundLevel
ile sınırlı; düşmelerde mini “bounce” (% 30 geri sekme) var.
3.4 Kontrol Sistemi
| Girdi | İşlev |
| -------------- | --------------------------------------------- |
| Ok tuşları | İleri/geri hareket, sola/sağa gövde rotasyonu |
| Space | Zıplama |
| Shift | Hızlı alçalma (negatif y-eksen hızı) |
| Mouse Drag | Kamerayı yörünge biçiminde döndürme |
3.5 UI Panel – Gerçek Zamanlı Ayar
Slider/Color Picker değerleri this.settings
objesine yazılır,
ardından applySettings()
ile sahneye yansıtılır.
Örneğin “Örümcek Rengi” değeri değiştiğinde:
Ana gövde rengi hex’e dönüştürülür.
Bacak rengi aynı tonun % +25 parlak hâli, segment rengi % –15 koyu hâli olur (helper adjustBrightness
).
“Ayarları Sıfırla” tüm slider’ları varsayılan değerlere geri getirir, fizik parametrelerini de temizler.
3.6 Animasyon Döngüsü
animate()
→ requestAnimationFrame
zinciri
updateBugMovement(): Fizik + kullanıcının girdi eylemleri.
updateBugAnimation():
Wave gait adı verilen örümcek yürüyüş düzeni (bacak faz kaymaları).
Vücut salınımı (bobAmount
) ve segment titreşimleri.
Havada iken koruma pozisyonu.
updateCamera(): Dinamik takip, ayarlanabilir yükseklik & mesafe.
renderer.render(scene,camera)
.
4. Kullanıcı Deneyimi ve Senaryolar
- Eğitim Amaçlı Biyoloji Simülasyonu
Gerçek anatomik eklem dizilimi sayesinde, örümceklerin yürüme mekaniğini gözlemlemek için ideal.
- Oyun Prototipleme
3D düşman karakteri ya da evcil yaratık davranışları test etmek isteyen oyun geliştiricileri, parametreleri hızlıca değiştirip farklı hissiyatlar deneyebilir.
- Web Tasarım/Demoscene
Portfolyo sitelerine veya etkileşimli sanat projelerine “wow-effect” katacak bir kahraman bileşeni.
5. Performans ve İyileştirme Fikirleri
| Alan | Şu anki Çözüm | Geliştirme Önerisi |
| ------------------- | -------------------------------------- | --------------------------------------------------------------------- |
| Gölgeler | PCFSoftShadowMap (yüksek çözünürlüklü) | Dinamik çözünürlük düşürme veya gölge kapatma seçeneği |
| Fizik | El yazımı basit kinematik | Cannon.js veya Ammo.js entegrasyonu ile gerçek rigid-body çarpışmalar |
| Mesh Detayı | Çok sayıda küçük geometri | BufferGeometry birleştirme veya Instancing ile draw-call azaltma |
| Material Shader | Standart Phong | Custom GLSL shader ile tüy, bump map veya çevrim içi tekstür desteği |
| Mobil Uyumluluk | Sadece responsive CSS | Düşük poligon LOD seviyesi, dokunmatik joystick eklentisi |
6. Sonuç
Bu kod, modern web teknolojilerinin bir tarayıcı içinde ne kadar ileri gidebileceğinin somut göstergesidir.
- Three.js ile modellenen ayrıntılı örümcek,
- CSS glass-morphism paneliyle entegre canlı ayarlar,
- Basit ama etkili bir kinematik-fizik katmanı
sayesinde, hem eğitici hem de eğlenceli bir 3D deneyim yaratır.
Projeyi genişletmek için:
- Düşman algı sistemi ekleyebilir,
- VR/AR entegrasyonu (WebXR) deneyebilir,
- Davranış ağaçları veya makine öğrenimi ile otonom hareket senaryoları yazabilirsiniz.
Böylece kod, yalnızca “ekransavarı” değil; interaktif 3D biyoloji, oyun geliştirme ve deneysel web sanatı için esnek bir temel halini alır.

<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Gerçekçi İnteraktif Örümcek</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--primary-color: #00e5ff;
--secondary-color: #0070f3;
--background-dark: #001122;
--text-color: #ffffff;
--panel-bg: rgba(0, 20, 40, 0.95);
--border-color: rgba(0, 229, 255, 0.3);
--hover-color: rgba(0, 229, 255, 0.1);
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, var(--background-dark) 0%, #003366 100%);
height: 100vh;
overflow: hidden;
position: relative;
color: var(--text-color);
}
#canvas-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
/* Kontrol Paneli */
.control-panel {
position: fixed;
top: 20px;
right: 20px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 20px;
min-width: 300px;
max-height: 85vh;
overflow-y: auto;
backdrop-filter: blur(10px);
z-index: 1000;
transition: transform 0.3s ease;
}
.control-panel.minimized {
transform: translateX(calc(100% - 50px));
}
.panel-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
}
.panel-title {
font-size: 18px;
font-weight: 600;
color: var(--primary-color);
}
.toggle-btn {
background: none;
border: 1px solid var(--primary-color);
color: var(--primary-color);
border-radius: 6px;
padding: 6px 12px;
cursor: pointer;
font-size: 12px;
transition: all 0.3s ease;
}
.toggle-btn:hover {
background: var(--hover-color);
}
/* Form Kontrolleri */
.control-group {
margin-bottom: 18px;
}
.control-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: var(--text-color);
}
.range-input {
width: 100%;
height: 6px;
border-radius: 3px;
background: rgba(0, 0, 0, 0.3);
outline: none;
-webkit-appearance: none;
}
.range-input::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--primary-color);
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 229, 255, 0.3);
}
.range-input::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: var(--primary-color);
cursor: pointer;
border: none;
box-shadow: 0 2px 6px rgba(0, 229, 255, 0.3);
}
.color-input {
width: 100%;
height: 40px;
border: 1px solid var(--border-color);
border-radius: 6px;
background: transparent;
cursor: pointer;
}
.value-display {
display: inline-block;
margin-left: 10px;
color: var(--primary-color);
font-weight: 600;
}
.section-title {
font-size: 16px;
color: var(--primary-color);
margin: 20px 0 10px 0;
padding-bottom: 5px;
border-bottom: 1px solid var(--border-color);
}
/* Reset Button */
.reset-btn {
width: 100%;
padding: 10px;
background: linear-gradient(135deg, var(--secondary-color), var(--primary-color));
border: none;
border-radius: 6px;
color: white;
font-weight: 600;
cursor: pointer;
transition: transform 0.2s ease;
margin-top: 10px;
}
.reset-btn:hover {
transform: translateY(-2px);
}
/* Kontrol Talimatları */
.instructions {
position: fixed;
bottom: 20px;
left: 20px;
background: var(--panel-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 15px;
backdrop-filter: blur(10px);
font-size: 14px;
line-height: 1.5;
z-index: 1000;
}
.instructions h4 {
color: var(--primary-color);
margin-bottom: 10px;
}
.key {
display: inline-block;
background: rgba(0, 229, 255, 0.2);
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
margin: 0 2px;
}
/* Loading */
.loading {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: var(--primary-color);
font-size: 18px;
z-index: 2000;
}
/* Responsive */
@media (max-width: 768px) {
.control-panel {
right: 10px;
top: 10px;
min-width: 280px;
}
.instructions {
left: 10px;
bottom: 10px;
font-size: 12px;
}
}
</style>
</head>
<body>
<!-- Loading -->
<div class="loading" id="loading">3D Örümcek Yükleniyor...</div>
<!-- Canvas Container -->
<div id="canvas-container"></div>
<!-- Kontrol Paneli -->
<div class="control-panel" id="controlPanel">
<div class="panel-header">
<h3 class="panel-title">3D Örümcek Ayarları</h3>
<button class="toggle-btn" id="togglePanel">Gizle</button>
</div>
<div class="section-title">Örümcek Özellikleri</div>
<div class="control-group">
<label class="control-label">Boyut <span class="value-display" id="sizeValue">1.0</span></label>
<input type="range" class="range-input" id="sizeRange" min="0.5" max="3" step="0.1" value="1.0">
</div>
<div class="control-group">
<label class="control-label">Hız <span class="value-display" id="speedValue">0.15</span></label>
<input type="range" class="range-input" id="speedRange" min="0.05" max="0.5" step="0.01" value="0.15">
</div>
<div class="control-group">
<label class="control-label">Dönüş Hızı <span class="value-display" id="turnSpeedValue">0.05</span></label>
<input type="range" class="range-input" id="turnSpeedRange" min="0.01" max="0.15" step="0.01" value="0.05">
</div>
<div class="control-group">
<label class="control-label">Örümcek Rengi</label>
<input type="color" class="color-input" id="colorPicker" value="#5d4e3a">
</div>
<div class="section-title">Animasyon</div>
<div class="control-group">
<label class="control-label">Yürüyüş Hızı <span class="value-display" id="walkSpeedValue">1.0</span></label>
<input type="range" class="range-input" id="walkSpeedRange" min="0.5" max="3" step="0.1" value="1.0">
</div>
<div class="control-group">
<label class="control-label">Vücut Salınımı <span class="value-display" id="bobValue">0.1</span></label>
<input type="range" class="range-input" id="bobRange" min="0" max="0.3" step="0.01" value="0.1">
</div>
<div class="control-group">
<label class="control-label">Bacak Koordinasyonu <span class="value-display" id="legValue">0.3</span></label>
<input type="range" class="range-input" id="legRange" min="0.1" max="0.8" step="0.05" value="0.3">
</div>
<div class="section-title">Fizik Motoru</div>
<div class="control-group">
<label class="control-label">Zıplama Kuvveti <span class="value-display" id="jumpValue">0.25</span></label>
<input type="range" class="range-input" id="jumpRange" min="0.1" max="0.5" step="0.05" value="0.25">
</div>
<div class="control-group">
<label class="control-label">Yerçekimi <span class="value-display" id="gravityValue">0.02</span></label>
<input type="range" class="range-input" id="gravityRange" min="0.01" max="0.05" step="0.005" value="0.02">
</div>
<div class="control-group">
<label class="control-label">Sürtünme <span class="value-display" id="frictionValue">0.95</span></label>
<input type="range" class="range-input" id="frictionRange" min="0.8" max="0.99" step="0.01" value="0.95">
</div>
<div class="section-title">3D Kamera</div>
<div class="control-group">
<label class="control-label">Kamera Uzaklığı <span class="value-display" id="cameraDistValue">10</span></label>
<input type="range" class="range-input" id="cameraDistRange" min="5" max="20" step="0.5" value="10">
</div>
<div class="control-group">
<label class="control-label">Kamera Yüksekliği <span class="value-display" id="cameraHeightValue">5</span></label>
<input type="range" class="range-input" id="cameraHeightRange" min="1" max="15" step="0.5" value="5">
</div>
<div class="section-title">Işıklandırma</div>
<div class="control-group">
<label class="control-label">Işık Şiddeti <span class="value-display" id="lightIntensityValue">1.0</span></label>
<input type="range" class="range-input" id="lightIntensityRange" min="0.2" max="2" step="0.1" value="1.0">
</div>
<div class="control-group">
<label class="control-label">Çevre Işığı <span class="value-display" id="ambientValue">0.4</span></label>
<input type="range" class="range-input" id="ambientRange" min="0.1" max="1" step="0.05" value="0.4">
</div>
<button class="reset-btn" id="resetBtn">Ayarları Sıfırla</button>
</div>
<!-- Kontrol Talimatları -->
<div class="instructions">
<h4>3D Örümcek Kontrolleri</h4>
<div><span class="key">↑</span> İleri Git</div>
<div><span class="key">↓</span> Geri Git</div>
<div><span class="key">←</span> Sola Dön</div>
<div><span class="key">→</span> Sağa Dön</div>
<div><span class="key">Space</span> Yukarı Tırman</div>
<div><span class="key">Shift</span> Aşağı İn</div>
<div><span class="key">Mouse</span> Kamerayı Çevir</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
class Bug3D {
constructor() {
// Fizik değişkenlerini en başta tanımla
this.velocity = { x: 0, y: 0, z: 0 };
this.isJumping = false;
this.onGround = true;
this.gravity = -0.02;
this.jumpForce = 0.25;
this.friction = 0.95;
this.groundLevel = 0.8;
this.bugPosition = { x: 0, y: 0.8, z: 0 };
this.bugRotation = 0;
this.time = 0;
this.isWalking = false;
this.initThreeJS();
this.initBug();
this.initControls();
this.initEventListeners();
this.initSettings();
this.animate();
document.getElementById('loading').style.display = 'none';
}
initThreeJS() {
// Scene
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(0x001122);
// Camera
this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.camera.position.set(0, 5, 10);
// Renderer
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.shadowMap.enabled = true;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.getElementById('canvas-container').appendChild(this.renderer.domElement);
// Lights
this.ambientLight = new THREE.AmbientLight(0x404040, 0.4);
this.scene.add(this.ambientLight);
this.directionalLight = new THREE.DirectionalLight(0xffffff, 1);
this.directionalLight.position.set(5, 10, 5);
this.directionalLight.castShadow = true;
this.directionalLight.shadow.mapSize.width = 2048;
this.directionalLight.shadow.mapSize.height = 2048;
this.scene.add(this.directionalLight);
// Ground
const groundGeometry = new THREE.PlaneGeometry(50, 50);
const groundMaterial = new THREE.MeshLambertMaterial({ color: 0x004466 });
this.ground = new THREE.Mesh(groundGeometry, groundMaterial);
this.ground.rotation.x = -Math.PI / 2;
this.ground.receiveShadow = true;
this.scene.add(this.ground);
// Grid
const gridHelper = new THREE.GridHelper(50, 50, 0x00e5ff, 0x003366);
this.scene.add(gridHelper);
}
initBug() {
this.bugGroup = new THREE.Group();
this.legs = [];
this.animationMixers = [];
// Materials - Gerçek örümcek renkleri
this.bodyMaterial = new THREE.MeshPhongMaterial({
color: 0x4a3429, // Kahverengi vücut
shininess: 30
});
this.legMaterial = new THREE.MeshPhongMaterial({
color: 0x6b4e3d, // Daha açık kahverengi bacaklar
shininess: 20
});
this.eyeMaterial = new THREE.MeshPhongMaterial({
color: 0x000000, // Siyah gözler
shininess: 100
});
// Cephalothorax (baş-göğüs birleşimi)
const cephalothoraxGeometry = new THREE.SphereGeometry(0.4, 16, 12);
cephalothoraxGeometry.scale(1, 0.8, 1.3); // Oval şekil
this.cephalothorax = new THREE.Mesh(cephalothoraxGeometry, this.bodyMaterial);
this.cephalothorax.castShadow = true;
this.cephalothorax.position.set(0, 0.4, 0.2);
this.bugGroup.add(this.cephalothorax);
// Abdomen (karın bölgesi) - daha büyük ve oval
const abdomenGeometry = new THREE.SphereGeometry(0.5, 16, 12);
abdomenGeometry.scale(1, 1.2, 1.8); // Uzun oval
this.abdomen = new THREE.Mesh(abdomenGeometry, this.bodyMaterial);
this.abdomen.castShadow = true;
this.abdomen.position.set(0, 0.5, -0.6);
this.bugGroup.add(this.abdomen);
// Pedipalps (ön uzuvlar)
this.createPedipalps();
// Chelicerae (çene yapısı)
this.createChelicerae();
// 8 Göz sistemi (gerçek örümcek gibi)
this.createEyes();
// 8 Bacak
this.createLegs();
// Position bug
this.bugGroup.position.y = 0;
this.scene.add(this.bugGroup);
// Bug properties
this.bugPosition = { x: 0, y: 0, z: 0 };
this.bugRotation = 0;
this.isWalking = false;
this.time = 0;
}
createPedipalps() {
// Sol pedipalp - Daha küçük
const pedipalGeometry = new THREE.CylinderGeometry(0.02, 0.03, 0.2);
this.leftPedipalp = new THREE.Mesh(pedipalGeometry, this.legMaterial);
this.leftPedipalp.position.set(-0.1, 0.3, 0.35);
this.leftPedipalp.rotation.z = -0.2;
this.leftPedipalp.rotation.x = 0.3;
this.leftPedipalp.castShadow = true;
this.bugGroup.add(this.leftPedipalp);
// Sağ pedipalp
this.rightPedipalp = new THREE.Mesh(pedipalGeometry, this.legMaterial);
this.rightPedipalp.position.set(0.1, 0.3, 0.35);
this.rightPedipalp.rotation.z = 0.2;
this.rightPedipalp.rotation.x = 0.3;
this.rightPedipalp.castShadow = true;
this.bugGroup.add(this.rightPedipalp);
}
createChelicerae() {
// Sol chelicera - Kırmızımsı renk
const cheliceraeGeometry = new THREE.CylinderGeometry(0.015, 0.03, 0.15);
this.leftChelicera = new THREE.Mesh(cheliceraeGeometry, this.cheliceraeMaterial);
this.leftChelicera.position.set(-0.06, 0.25, 0.4);
this.leftChelicera.rotation.x = 0.4;
this.leftChelicera.castShadow = true;
this.bugGroup.add(this.leftChelicera);
// Sağ chelicera
this.rightChelicera = new THREE.Mesh(cheliceraeGeometry, this.cheliceraeMaterial);
this.rightChelicera.position.set(0.06, 0.25, 0.4);
this.rightChelicera.rotation.x = 0.4;
this.rightChelicera.castShadow = true;
this.bugGroup.add(this.rightChelicera);
// Fangs (zehir dişleri) - Daha belirgin
const fangGeometry = new THREE.CylinderGeometry(0.003, 0.008, 0.08);
const fangMaterial = new THREE.MeshPhongMaterial({ color: 0x2c1810 });
this.leftFang = new THREE.Mesh(fangGeometry, fangMaterial);
this.leftFang.position.set(-0.06, 0.18, 0.45);
this.leftFang.rotation.x = 0.6;
this.bugGroup.add(this.leftFang);
this.rightFang = new THREE.Mesh(fangGeometry, fangMaterial);
this.rightFang.position.set(0.06, 0.18, 0.45);
this.rightFang.rotation.x = 0.6;
this.bugGroup.add(this.rightFang);
}
createEyes() {
// Ana gözler (daha küçük ve gerçekçi)
const mainEyeGeometry = new THREE.SphereGeometry(0.025, 8, 6);
// Sol ana göz
this.mainLeftEye = new THREE.Mesh(mainEyeGeometry, this.eyeMaterial);
this.mainLeftEye.position.set(-0.08, 0.4, 0.28);
this.bugGroup.add(this.mainLeftEye);
// Sağ ana göz
this.mainRightEye = new THREE.Mesh(mainEyeGeometry, this.eyeMaterial);
this.mainRightEye.position.set(0.08, 0.4, 0.28);
this.bugGroup.add(this.mainRightEye);
// Yan gözler (çok küçük)
const sideEyeGeometry = new THREE.SphereGeometry(0.015, 6, 4);
this.sideEyes = [];
const eyePositions = [
{ x: -0.12, y: 0.38, z: 0.2 }, // Sol yan
{ x: 0.12, y: 0.38, z: 0.2 }, // Sağ yan
{ x: -0.05, y: 0.42, z: 0.3 }, // Sol üst
{ x: 0.05, y: 0.42, z: 0.3 }, // Sağ üst
{ x: -0.1, y: 0.35, z: 0.15 }, // Sol alt
{ x: 0.1, y: 0.35, z: 0.15 } // Sağ alt
];
eyePositions.forEach(pos => {
const eye = new THREE.Mesh(sideEyeGeometry, this.eyeMaterial);
eye.position.set(pos.x, pos.y, pos.z);
this.sideEyes.push(eye);
this.bugGroup.add(eye);
});
}
createLegs() {
// Gerçekçi örümcek bacak pozisyonları (8 bacak) - ÇOK DAHA UZUN bacaklar
const legPositions = [
// Sol bacaklar (önden arkaya) - Gerçekçi açılar
{ x: -0.25, z: 0.15, side: 'left', index: 0, angle: -30 }, // L1
{ x: -0.25, z: 0.05, side: 'left', index: 1, angle: -60 }, // L2
{ x: -0.25, z: -0.05, side: 'left', index: 2, angle: -120 }, // L3
{ x: -0.25, z: -0.15, side: 'left', index: 3, angle: -150 }, // L4
// Sağ bacaklar (önden arkaya)
{ x: 0.25, z: 0.15, side: 'right', index: 0, angle: 30 }, // R1
{ x: 0.25, z: 0.05, side: 'right', index: 1, angle: 60 }, // R2
{ x: 0.25, z: -0.05, side: 'right', index: 2, angle: 120 }, // R3
{ x: 0.25, z: -0.15, side: 'right', index: 3, angle: 150 } // R4
];
legPositions.forEach((pos, i) => {
// Ana bacak grubu - cephalothorax'a bağlı
const legGroup = new THREE.Group();
legGroup.position.set(pos.x, 0.2, pos.z); // Vücuda daha yakın
legGroup.rotation.y = (pos.angle * Math.PI) / 180;
// Coxa (kalça) - vücuda en yakın segment
const coxaGeometry = new THREE.CylinderGeometry(0.025, 0.035, 0.15);
const coxa = new THREE.Mesh(coxaGeometry, this.legMaterial);
coxa.rotation.z = Math.PI / 2;
coxa.position.y = 0.05;
coxa.castShadow = true;
legGroup.add(coxa);
// Trochanter (küçük bağlantı segmenti)
const trochanterGroup = new THREE.Group();
trochanterGroup.position.set(0.1, 0.02, 0);
legGroup.add(trochanterGroup);
const trochanterGeometry = new THREE.SphereGeometry(0.02, 8, 6);
const trochanter = new THREE.Mesh(trochanterGeometry, this.legSegmentMaterial);
trochanter.castShadow = true;
trochanterGroup.add(trochanter);
// Femur (uyluk) - ÇOK UZUN segment
const femurGroup = new THREE.Group();
trochanterGroup.add(femurGroup);
const femurGeometry = new THREE.CylinderGeometry(0.015, 0.02, 2.0); // ÇOK UZUN
const femur = new THREE.Mesh(femurGeometry, this.legMaterial);
femur.position.set(1.0, -0.2, 0); // Uzun ve aşağı
femur.rotation.z = Math.PI / 2;
femur.castShadow = true;
femurGroup.add(femur);
// Femur segmentleri (halka desenler)
for (let s = 0; s < 4; s++) {
const segmentGeometry = new THREE.CylinderGeometry(0.018, 0.022, 0.08);
const segment = new THREE.Mesh(segmentGeometry, this.legSegmentMaterial);
segment.position.set(0.3 + s * 0.4, -0.2, 0);
segment.rotation.z = Math.PI / 2;
segment.castShadow = true;
femurGroup.add(segment);
}
// Patella (diz kapağı)
const patellaGroup = new THREE.Group();
patellaGroup.position.set(2.0, -0.4, 0); // Çok uzakta
femurGroup.add(patellaGroup);
const patellaGeometry = new THREE.SphereGeometry(0.018, 6, 4);
const patella = new THREE.Mesh(patellaGeometry, this.legSegmentMaterial);
patella.castShadow = true;
patellaGroup.add(patella);
// Tibia (kaval kemiği) - ÇOK UZUN
const tibiaGroup = new THREE.Group();
patellaGroup.add(tibiaGroup);
const tibiaGeometry = new THREE.CylinderGeometry(0.012, 0.018, 1.8); // ÇOK UZUN
const tibia = new THREE.Mesh(tibiaGeometry, this.legMaterial);
tibia.position.set(0.9, -0.3, 0);
tibia.rotation.z = Math.PI / 2;
tibia.castShadow = true;
tibiaGroup.add(tibia);
// Tibia segmentleri (halka desenler)
for (let s = 0; s < 3; s++) {
const segmentGeometry = new THREE.CylinderGeometry(0.015, 0.02, 0.1);
const segment = new THREE.Mesh(segmentGeometry, this.legSegmentMaterial);
segment.position.set(0.2 + s * 0.5, -0.3, 0);
segment.rotation.z = Math.PI / 2;
segment.castShadow = true;
tibiaGroup.add(segment);
}
// Metatarsus (ayak üstü) - UZUN
const metatarsusGroup = new THREE.Group();
metatarsusGroup.position.set(1.8, -0.6, 0);
tibiaGroup.add(metatarsusGroup);
const metatarsusGeometry = new THREE.CylinderGeometry(0.01, 0.015, 0.8);
const metatarsus = new THREE.Mesh(metatarsusGeometry, this.legMaterial);
metatarsus.position.set(0.4, -0.1, 0);
metatarsus.rotation.z = Math.PI / 2;
metatarsus.castShadow = true;
metatarsusGroup.add(metatarsus);
// Metatarsus segmentleri
for (let s = 0; s < 2; s++) {
const segmentGeometry = new THREE.CylinderGeometry(0.012, 0.017, 0.06);
const segment = new THREE.Mesh(segmentGeometry, this.legSegmentMaterial);
segment.position.set(0.1 + s * 0.3, -0.1, 0);
segment.rotation.z = Math.PI / 2;
segment.castShadow = true;
metatarsusGroup.add(segment);
}
// Tarsus (ayak) - Çok segmentli ve uzun
const tarsusGroup = new THREE.Group();
tarsusGroup.position.set(0.8, -0.2, 0);
metatarsusGroup.add(tarsusGroup);
// Çoklu tarsus segmentleri
for (let s = 0; s < 5; s++) {
const tarsusSegmentGeometry = new THREE.CylinderGeometry(0.008, 0.012, 0.12);
const tarsusSegment = new THREE.Mesh(tarsusSegmentGeometry, s % 2 === 0 ? this.legMaterial : this.legSegmentMaterial);
tarsusSegment.position.set(s * 0.1, -0.05, 0);
tarsusSegment.rotation.z = Math.PI / 2;
tarsusSegment.castShadow = true;
tarsusGroup.add(tarsusSegment);
}
// Claws (pençeler) - ayağın ucunda, daha uzun ve sivri
const clawGroup = new THREE.Group();
clawGroup.position.set(0.5, -0.08, 0);
tarsusGroup.add(clawGroup);
for (let c = 0; c < 2; c++) {
const clawGeometry = new THREE.CylinderGeometry(0.002, 0.006, 0.1);
const claw = new THREE.Mesh(clawGeometry, new THREE.MeshPhongMaterial({ color: 0x1a1a1a }));
const angle = (c - 0.5) * 0.6;
claw.position.set(0.05, -0.02, Math.sin(angle) * 0.02);
claw.rotation.z = Math.PI / 2 + angle;
claw.rotation.x = 0.3; // Sivri açı
claw.castShadow = true;
clawGroup.add(claw);
}
this.legs.push({
group: legGroup,
trochanterGroup: trochanterGroup,
femurGroup: femurGroup,
patellaGroup: patellaGroup,
tibiaGroup: tibiaGroup,
metatarsusGroup: metatarsusGroup,
tarsusGroup: tarsusGroup,
coxa: coxa,
femur: femur,
tibia: tibia,
side: pos.side,
index: pos.index,
legIndex: i,
originalPosition: { ...pos },
// Gerçek örümcek eklem açıları - Uzun bacaklar için
baseRotations: {
coxa: 0,
femur: pos.side === 'left' ? 0.3 : -0.3, // Dışa doğru açılım
tibia: -0.4, // Aşağı doğru bükülme
metatarsus: 0.2 // Hafif yukarı kıvrılma
}
});
this.bugGroup.add(legGroup);
});
}
initControls() {
this.keys = {};
this.mouse = { x: 0, y: 0 };
this.mousePressed = false;
// Mouse controls
this.renderer.domElement.addEventListener('mousedown', (e) => {
this.mousePressed = true;
});
this.renderer.domElement.addEventListener('mouseup', (e) => {
this.mousePressed = false;
});
this.renderer.domElement.addEventListener('mousemove', (e) => {
if (this.mousePressed) {
const deltaX = e.movementX * 0.01;
const deltaY = e.movementY * 0.01;
this.cameraAngleX += deltaX;
this.cameraAngleY += deltaY;
this.cameraAngleY = Math.max(-Math.PI/3, Math.min(Math.PI/3, this.cameraAngleY));
}
});
// Keyboard
document.addEventListener('keydown', (e) => {
this.keys[e.code] = true;
this.updateWalkingState();
});
document.addEventListener('keyup', (e) => {
this.keys[e.code] = false;
this.updateWalkingState();
});
// Camera angles
this.cameraAngleX = 0;
this.cameraAngleY = 0;
}
initSettings() {
this.settings = {
size: 1.0,
speed: 0.15,
turnSpeed: 0.05,
bugColor: '#5d4e3a', // Yeni gerçekçi kahverengi
walkSpeed: 1.0,
bobAmount: 0.1,
legMovement: 0.3,
jumpForce: 0.25,
gravity: 0.02,
friction: 0.95,
cameraDistance: 10,
cameraHeight: 5,
lightIntensity: 1.0,
ambient: 0.4
};
this.initControlPanel();
}
initControlPanel() {
// Panel toggle
document.getElementById('togglePanel').addEventListener('click', () => {
const panel = document.getElementById('controlPanel');
panel.classList.toggle('minimized');
const btn = document.getElementById('togglePanel');
btn.textContent = panel.classList.contains('minimized') ? 'Göster' : 'Gizle';
});
// Controls - Safe access with existence check
const controls = {
size: document.getElementById('sizeRange'),
speed: document.getElementById('speedRange'),
turnSpeed: document.getElementById('turnSpeedRange'),
bugColor: document.getElementById('colorPicker'),
walkSpeed: document.getElementById('walkSpeedRange'),
bobAmount: document.getElementById('bobRange'),
legMovement: document.getElementById('legRange'),
jumpForce: document.getElementById('jumpRange'),
gravity: document.getElementById('gravityRange'),
friction: document.getElementById('frictionRange'),
cameraDistance: document.getElementById('cameraDistRange'),
cameraHeight: document.getElementById('cameraHeightRange'),
lightIntensity: document.getElementById('lightIntensityRange'),
ambient: document.getElementById('ambientRange')
};
Object.keys(controls).forEach(key => {
if (controls[key]) { // Element varsa event listener ekle
controls[key].addEventListener('input', (e) => {
this.updateSetting(key, e.target.value);
});
}
});
// Reset button
document.getElementById('resetBtn').addEventListener('click', () => {
this.resetSettings();
});
this.updateAllDisplays();
}
updateSetting(key, value) {
if (key === 'bugColor') {
this.settings[key] = value;
// Vücut ve bacak renklerini güncelle
const baseColor = parseInt(value.substring(1), 16);
this.bodyMaterial.color.setHex(baseColor);
// Bacakları biraz daha açık yap
const legColor = this.adjustBrightness(value, 25);
this.legMaterial.color.setHex(parseInt(legColor.substring(1), 16));
// Segment renklerini daha koyu yap
const segmentColor = this.adjustBrightness(value, -15);
this.legSegmentMaterial.color.setHex(parseInt(segmentColor.substring(1), 16));
} else {
this.settings[key] = parseFloat(value);
// Fizik ayarlarını gerçek zamanlı uygula
if (key === 'jumpForce') {
this.jumpForce = this.settings.jumpForce;
} else if (key === 'gravity') {
this.gravity = -this.settings.gravity; // Negatif değer
} else if (key === 'friction') {
this.friction = this.settings.friction;
}
}
this.updateDisplay(key, value);
this.applySettings();
}
updateDisplay(key, value) {
const display = document.getElementById(key + 'Value');
if (display) { // Element varsa güncelle
display.textContent = key === 'bugColor' ? value : parseFloat(value).toFixed(2);
}
}
updateAllDisplays() {
Object.keys(this.settings).forEach(key => {
this.updateDisplay(key, this.settings[key]);
});
}
applySettings() {
// Size
this.bugGroup.scale.setScalar(this.settings.size);
// Lights
this.directionalLight.intensity = this.settings.lightIntensity;
this.ambientLight.intensity = this.settings.ambient;
}
resetSettings() {
this.settings = {
size: 1.0,
speed: 0.15,
turnSpeed: 0.05,
bugColor: '#5d4e3a', // Gerçekçi kahverengi
walkSpeed: 1.0,
bobAmount: 0.1,
legMovement: 0.3,
jumpForce: 0.25,
gravity: 0.02,
friction: 0.95,
cameraDistance: 10,
cameraHeight: 5,
lightIntensity: 1.0,
ambient: 0.4
};
// Reset controls - Safe access
const resetElements = [
{ id: 'sizeRange', value: 1.0 },
{ id: 'speedRange', value: 0.15 },
{ id: 'turnSpeedRange', value: 0.05 },
{ id: 'colorPicker', value: '#5d4e3a' },
{ id: 'walkSpeedRange', value: 1.0 },
{ id: 'bobRange', value: 0.1 },
{ id: 'legRange', value: 0.3 },
{ id: 'jumpRange', value: 0.25 },
{ id: 'gravityRange', value: 0.02 },
{ id: 'frictionRange', value: 0.95 },
{ id: 'cameraDistRange', value: 10 },
{ id: 'cameraHeightRange', value: 5 },
{ id: 'lightIntensityRange', value: 1.0 },
{ id: 'ambientRange', value: 0.4 }
];
resetElements.forEach(item => {
const element = document.getElementById(item.id);
if (element) {
element.value = item.value;
}
});
this.updateAllDisplays();
this.applySettings();
// Reset position ve physics
this.bugPosition = { x: 0, y: this.groundLevel, z: 0 };
this.bugRotation = 0;
this.cameraAngleX = 0;
this.cameraAngleY = 0;
// Fizik değişkenlerini sıfırla
this.velocity = { x: 0, y: 0, z: 0 };
this.isJumping = false;
this.onGround = true;
// Fizik motorunu güncelle
this.jumpForce = this.settings.jumpForce;
this.gravity = -this.settings.gravity;
this.friction = this.settings.friction;
}
updateWalkingState() {
// Sadece yerdeyken yürüyüş animasyonu yapılsın
this.isWalking = this.onGround && (this.keys['ArrowUp'] || this.keys['ArrowDown'] ||
this.keys['ArrowLeft'] || this.keys['ArrowRight']);
}
updateBugMovement() {
// Değişkenlerin tanımlı olduğundan emin ol
if (!this.velocity || !this.bugPosition) return;
// Fizik güncellmeleri
this.updatePhysics();
// Yatay hareket kontrolleri (sadece yerdeyken)
if (this.onGround) {
// Rotation
if (this.keys['ArrowLeft']) {
this.bugRotation += this.settings.turnSpeed;
}
if (this.keys['ArrowRight']) {
this.bugRotation -= this.settings.turnSpeed;
}
// Movement - velocity bazlı
if (this.keys['ArrowUp']) {
this.velocity.x += Math.sin(this.bugRotation) * this.settings.speed * 0.5;
this.velocity.z += Math.cos(this.bugRotation) * this.settings.speed * 0.5;
}
if (this.keys['ArrowDown']) {
this.velocity.x -= Math.sin(this.bugRotation) * this.settings.speed * 0.3;
this.velocity.z -= Math.cos(this.bugRotation) * this.settings.speed * 0.3;
}
// Jump - Space tuşu
if (this.keys['Space'] && !this.isJumping) {
this.jump();
}
}
// Aşağı hareket (her zaman)
if (this.keys['ShiftLeft'] || this.keys['ShiftRight']) {
this.velocity.y -= this.settings.speed * 0.3;
}
// Velocity'yi pozisyona uygula
this.bugPosition.x += this.velocity.x;
this.bugPosition.y += this.velocity.y;
this.bugPosition.z += this.velocity.z;
// Yatay sürtünme (sadece yerdeyken)
if (this.onGround) {
this.velocity.x *= this.friction;
this.velocity.z *= this.friction;
}
// Sınırlar
this.bugPosition.x = Math.max(-20, Math.min(20, this.bugPosition.x));
this.bugPosition.z = Math.max(-20, Math.min(20, this.bugPosition.z));
this.bugPosition.y = Math.max(this.groundLevel, this.bugPosition.y);
// Eğer zemin seviyesinin altındaysa düzelt
if (this.bugPosition.y <= this.groundLevel) {
this.bugPosition.y = this.groundLevel;
this.velocity.y = 0;
this.onGround = true;
this.isJumping = false;
}
// Pozisyonu ve rotasyonu uygula
if (this.bugGroup) {
this.bugGroup.position.set(this.bugPosition.x, this.bugPosition.y, this.bugPosition.z);
this.bugGroup.rotation.y = this.bugRotation;
}
}
updatePhysics() {
// Değişkenlerin tanımlı olduğundan emin ol
if (!this.velocity || !this.bugPosition) return;
// Yerçekimi uygula (sadece havadayken)
if (!this.onGround || this.bugPosition.y > this.groundLevel) {
this.velocity.y += this.gravity;
this.onGround = false;
}
// Terminal velocity (maksimum düşme hızı)
this.velocity.y = Math.max(-0.5, this.velocity.y);
// Havada sürtünme (hava direnci)
if (!this.onGround) {
this.velocity.x *= 0.98; // Hafif hava direnci
this.velocity.z *= 0.98;
}
// Zemin kontrolü
if (this.bugPosition.y <= this.groundLevel && this.velocity.y <= 0) {
this.bugPosition.y = this.groundLevel;
// Yere çarpma efekti - bounce
if (this.velocity.y < -0.1) {
this.velocity.y = -this.velocity.y * 0.3; // %30 geri sekme
// Çok küçük bounce'ları yok say
if (Math.abs(this.velocity.y) < 0.05) {
this.velocity.y = 0;
this.onGround = true;
this.isJumping = false;
}
} else {
this.velocity.y = 0;
this.onGround = true;
this.isJumping = false;
}
}
}
jump() {
if (!this.velocity || !this.legs) return; // Güvenlik kontrolü
if (this.onGround && !this.isJumping) {
this.velocity.y = this.jumpForce;
this.onGround = false;
this.isJumping = true;
// Zıplama animasyonu için bacakları biraz büksün
this.legs.forEach(leg => {
if (leg && leg.tibiaGroup) {
leg.tibiaGroup.rotation.z += 0.2;
}
});
// 200ms sonra bacakları normale döndür
setTimeout(() => {
if (this.legs) {
this.legs.forEach(leg => {
if (leg && leg.tibiaGroup) {
leg.tibiaGroup.rotation.z -= 0.2;
}
});
}
}, 200);
}
}
updateBugAnimation() {
this.time += 0.016 * this.settings.walkSpeed;
if (this.isWalking && this.onGround) {
// Gerçek örümcek vücut hareketi - hafif salınım (sadece yerdeyken)
this.cephalothorax.position.y = 0.4 + Math.sin(this.time * 6) * this.settings.bobAmount * 0.5;
this.abdomen.position.y = 0.5 + Math.sin(this.time * 6 + 0.5) * this.settings.bobAmount;
// Pedipalp hareketi
this.leftPedipalp.rotation.x = 0.2 + Math.sin(this.time * 4) * 0.1;
this.rightPedipalp.rotation.x = 0.2 + Math.sin(this.time * 4 + Math.PI) * 0.1;
// Chelicerae hareketi (beslenirken hareket eder)
this.leftChelicera.rotation.x = 0.5 + Math.sin(this.time * 8) * 0.05;
this.rightChelicera.rotation.x = 0.5 + Math.sin(this.time * 8 + Math.PI) * 0.05;
// Örümcek yürüyüş deseni: Wave gait (dalga hareketi)
this.legs.forEach((leg, i) => {
let legPhase = 0;
// Gerçek örümcek yürüyüş deseni
switch(leg.legIndex) {
case 0: legPhase = 0; break; // L1
case 1: legPhase = Math.PI; break; // L2
case 2: legPhase = Math.PI/2; break; // L3
case 3: legPhase = 3*Math.PI/2; break; // L4
case 4: legPhase = 3*Math.PI/2; break; // R1
case 5: legPhase = Math.PI/2; break; // R2
case 6: legPhase = Math.PI; break; // R3
case 7: legPhase = 0; break; // R4
}
const legTime = this.time * 4 + legPhase;
const legCycle = Math.sin(legTime);
const legCyclePositive = Math.max(0, legCycle);
const swingAmount = this.settings.legMovement;
// Coxa rotation (vücuda bağlı segment)
const coxaSwing = legCycle * swingAmount * 0.3;
leg.trochanterGroup.rotation.y = coxaSwing;
// Femur rotation (ana bacak segmenti) - Düzeltilmiş
const femurBend = leg.baseRotations.femur + Math.sin(legTime + Math.PI/4) * swingAmount * 0.5;
leg.femurGroup.rotation.z = femurBend;
// Tibia rotation (alt bacak segmenti) - Düzeltilmiş
const tibiaBend = leg.baseRotations.tibia + Math.sin(legTime + Math.PI/2) * swingAmount * 0.6;
leg.tibiaGroup.rotation.z = tibiaBend;
// Metatarsus rotation (ayak öncesi segment) - Düzeltilmiş
const metatarsusBend = leg.baseRotations.metatarsus + Math.sin(legTime + 3*Math.PI/4) * swingAmount * 0.4;
leg.metatarsusGroup.rotation.z = metatarsusBend;
// Tarsus (ayak) hareketi
const tarsusBend = Math.sin(legTime + Math.PI) * swingAmount * 0.2;
leg.tarsusGroup.rotation.z = tarsusBend;
// Bacak kaldırma - uzun bacaklar için çok minimal
const footLift = legCyclePositive * 0.02; // Çok az kaldırma
leg.group.position.y = 0.2 + footLift; // Başlangıç Y=0.2
// İleri-geri hareket - uzun bacaklar için daha az
const forwardMotion = Math.sin(legTime + Math.PI/6) * 0.02;
leg.group.position.z = leg.originalPosition.z + forwardMotion;
// Yan hareket - uzun bacaklar için çok minimal
const sideMotion = Math.sin(legTime) * 0.005;
leg.group.position.x = leg.originalPosition.x + (leg.side === 'left' ? -sideMotion : sideMotion);
});
} else {
// Dinlenme pozisyonu - örümcekler çok hafif hareket eder
this.cephalothorax.position.y = 0.4 + Math.sin(this.time) * 0.005;
this.abdomen.position.y = 0.5 + Math.sin(this.time + 0.5) * 0.008;
// Havadayken farklı animasyon
if (!this.onGround) {
// Havada bacaklar biraz bükülerek koruma pozisyonu alır
this.legs.forEach((leg, i) => {
const targetTibia = leg.baseRotations.tibia - 0.3; // Daha bükümlü
leg.tibiaGroup.rotation.z += (targetTibia - leg.tibiaGroup.rotation.z) * 0.1;
leg.metatarsusGroup.rotation.z += (0.5 - leg.metatarsusGroup.rotation.z) * 0.1;
});
} else {
// Yerde normal dinlenme pozisyonu
// Pedipalp'ları normale döndür
this.leftPedipalp.rotation.x += (0.2 - this.leftPedipalp.rotation.x) * 0.05;
this.rightPedipalp.rotation.x += (0.2 - this.rightPedipalp.rotation.x) * 0.05;
// Chelicerae'yi normale döndür
this.leftChelicera.rotation.x += (0.5 - this.leftChelicera.rotation.x) * 0.05;
this.rightChelicera.rotation.x += (0.5 - this.rightChelicera.rotation.x) * 0.05;
// Bacakları dinlenme pozisyonuna getir
this.legs.forEach((leg, i) => {
// Yumuşak geçiş ile normal pozisyona dön
leg.trochanterGroup.rotation.y *= 0.95;
leg.femurGroup.rotation.z += (leg.baseRotations.femur - leg.femurGroup.rotation.z) * 0.05;
leg.tibiaGroup.rotation.z += (leg.baseRotations.tibia - leg.tibiaGroup.rotation.z) * 0.05;
leg.metatarsusGroup.rotation.z += (leg.baseRotations.metatarsus - leg.metatarsusGroup.rotation.z) * 0.05;
leg.tarsusGroup.rotation.z *= 0.95;
// Pozisyonu normale döndür
leg.group.position.y += (0.2 - leg.group.position.y) * 0.05; // Y=0.2 hedef
leg.group.position.z += (leg.originalPosition.z - leg.group.position.z) * 0.05;
leg.group.position.x += (leg.originalPosition.x - leg.group.position.x) * 0.05;
});
}
}
// Ana gözlerin hafif hareketi (av takibi simülasyonu)
const eyeMovement = Math.sin(this.time * 2) * 0.03;
this.mainLeftEye.rotation.y = eyeMovement;
this.mainRightEye.rotation.y = -eyeMovement;
// Yan gözlerin bağımsız hareketi
this.sideEyes.forEach((eye, i) => {
eye.rotation.y = Math.sin(this.time * 3 + i * 0.5) * 0.02;
});
}
updateCamera() {
const distance = this.settings.cameraDistance;
const height = this.settings.cameraHeight;
// Follow bug with offset - uzun bacaklar için daha yüksek kamera
const targetX = this.bugPosition.x - Math.sin(this.bugRotation + this.cameraAngleX) * distance;
const targetY = this.bugPosition.y + height + Math.sin(this.cameraAngleY) * distance + 2; // +2 extra height
const targetZ = this.bugPosition.z - Math.cos(this.bugRotation + this.cameraAngleX) * distance;
this.camera.position.set(targetX, targetY, targetZ);
this.camera.lookAt(this.bugPosition.x, this.bugPosition.y + 2, this.bugPosition.z); // +2 extra focus height
}
initEventListeners() {
window.addEventListener('resize', () => {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
});
}
animate() {
requestAnimationFrame(() => this.animate());
this.updateBugMovement();
this.updateBugAnimation();
this.updateCamera();
this.renderer.render(this.scene, this.camera);
}
}
// Initialize when page loads
window.addEventListener('load', () => {
new Bug3D();
});
</script>
</body>
</html>