/ app / spec / kamaji-threejs.html
kamaji-threejs.html
  1  <!DOCTYPE html>
  2  <html lang="en">
  3  <head>
  4      <meta charset="UTF-8">
  5      <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6      <title>Kamaji - Professional Three.js 3D</title>
  7      <style>
  8          body {
  9              margin: 0;
 10              padding: 0;
 11              overflow: hidden;
 12              font-family: Verdana, sans-serif;
 13              background: #0a0a0a;
 14          }
 15          #container {
 16              position: absolute;
 17              top: 0;
 18              left: 0;
 19              width: 100%;
 20              height: 100%;
 21              display: flex;
 22              justify-content: center;
 23              align-items: center;
 24          }
 25          canvas {
 26              border: 2px solid #ff6600;
 27              border-radius: 8px;
 28              box-shadow: 0 0 20px rgba(255, 102, 0, 0.3);
 29          }
 30          #info {
 31              position: absolute;
 32              top: 20px;
 33              left: 50%;
 34              transform: translateX(-50%);
 35              color: #ff6600;
 36              text-align: center;
 37              text-shadow: 0 0 10px rgba(255, 102, 0, 0.5);
 38              pointer-events: none;
 39              z-index: 100;
 40          }
 41          h1 {
 42              margin: 0;
 43              font-size: 1.8rem;
 44          }
 45          .version {
 46              color: #aaa;
 47              font-size: 0.9rem;
 48              margin-top: 0.5rem;
 49          }
 50          .controls {
 51              color: #aaa;
 52              font-size: 0.75rem;
 53              margin-top: 0.5rem;
 54          }
 55      </style>
 56  </head>
 57  <body>
 58      <div id="info">
 59          <h1>🔥 KAMAJI</h1>
 60          <div class="version">Professional Three.js • PBR Materials</div>
 61          <div class="controls">Drag to Rotate • Scroll to Zoom • Right-Click Pan</div>
 62      </div>
 63      <div id="container"></div>
 64  
 65      <script type="importmap">
 66      {
 67          "imports": {
 68              "three": "https://cdn.jsdelivr.net/npm/three@0.158.0/build/three.module.js",
 69              "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.158.0/examples/jsm/"
 70          }
 71      }
 72      </script>
 73  
 74      <script type="module">
 75          import * as THREE from 'three';
 76          import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 77  
 78          // === SCENE SETUP ===
 79          const scene = new THREE.Scene();
 80          scene.background = new THREE.Color(0x0a0a0a);
 81          scene.fog = new THREE.Fog(0x0a0a0a, 100, 300);
 82  
 83          // === CAMERA ===
 84          const camera = new THREE.PerspectiveCamera(
 85              50,
 86              600 / 600,
 87              0.1,
 88              1000
 89          );
 90          camera.position.set(0, 30, 180);
 91  
 92          // === RENDERER ===
 93          const renderer = new THREE.WebGLRenderer({
 94              antialias: true,
 95              alpha: false
 96          });
 97          renderer.setSize(600, 600);
 98          renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
 99          renderer.shadowMap.enabled = true;
100          renderer.shadowMap.type = THREE.PCFSoftShadowMap;
101          renderer.toneMapping = THREE.ACESFilmicToneMapping;
102          renderer.toneMappingExposure = 1.0;
103          document.getElementById('container').appendChild(renderer.domElement);
104  
105          // === PROFESSIONAL THREE-POINT LIGHTING ===
106  
107          // Key Light (main directional)
108          const keyLight = new THREE.DirectionalLight(0xffffff, 0.9);
109          keyLight.position.set(-30, 50, 40);
110          keyLight.castShadow = true;
111          keyLight.shadow.mapSize.width = 2048;
112          keyLight.shadow.mapSize.height = 2048;
113          keyLight.shadow.camera.near = 0.5;
114          keyLight.shadow.camera.far = 200;
115          keyLight.shadow.camera.left = -50;
116          keyLight.shadow.camera.right = 50;
117          keyLight.shadow.camera.top = 50;
118          keyLight.shadow.camera.bottom = -50;
119          scene.add(keyLight);
120  
121          // Fill Light (ambient hemisphere)
122          const hemisphereLight = new THREE.HemisphereLight(0xffffff, 0x444444, 0.6);
123          hemisphereLight.position.set(0, 50, 0);
124          scene.add(hemisphereLight);
125  
126          // Rim/Back Light (separation)
127          const rimLight = new THREE.DirectionalLight(0x8899ff, 0.4);
128          rimLight.position.set(30, 30, -40);
129          scene.add(rimLight);
130  
131          // === MATERIALS ===
132  
133          // Skin material (PBR)
134          const skinMaterial = new THREE.MeshStandardMaterial({
135              color: 0xd4a574,
136              roughness: 0.6,
137              metalness: 0.0
138          });
139  
140          // Shirt material (blue fabric)
141          const shirtMaterial = new THREE.MeshStandardMaterial({
142              color: 0x3a4f6b,
143              roughness: 0.8,
144              metalness: 0.0
145          });
146  
147          // Beard material (brown, rough)
148          const beardMaterial = new THREE.MeshStandardMaterial({
149              color: 0x6b5638,
150              roughness: 0.9,
151              metalness: 0.0
152          });
153  
154          // Glasses material (black, glossy with transmission)
155          const glassesMaterial = new THREE.MeshPhysicalMaterial({
156              color: 0x0a0a0a,
157              metalness: 0.0,
158              roughness: 0.1,
159              transparent: true,
160              transmission: 0.8,
161              opacity: 0.3,
162              reflectivity: 0.9,
163              clearcoat: 1.0,
164              clearcoatRoughness: 0.0
165          });
166  
167          // Glasses frame material (black, metallic)
168          const frameMaterial = new THREE.MeshStandardMaterial({
169              color: 0x1a1a1a,
170              roughness: 0.3,
171              metalness: 0.8
172          });
173  
174          // === CHARACTER GROUP ===
175          const kamaji = new THREE.Group();
176  
177          // === HEAD ===
178          const headGeo = new THREE.SphereGeometry(20, 32, 32);
179          const head = new THREE.Mesh(headGeo, skinMaterial);
180          head.position.y = 40;
181          head.castShadow = true;
182          head.receiveShadow = true;
183          kamaji.add(head);
184  
185          // === SUNGLASSES ===
186          const glassesGroup = new THREE.Group();
187  
188          // Left lens
189          const lensGeo = new THREE.SphereGeometry(7, 32, 32);
190          const leftLens = new THREE.Mesh(lensGeo, glassesMaterial);
191          leftLens.position.set(-9, 42, 17);
192          leftLens.scale.z = 0.5;
193          glassesGroup.add(leftLens);
194  
195          // Right lens
196          const rightLens = new THREE.Mesh(lensGeo, glassesMaterial);
197          rightLens.position.set(9, 42, 17);
198          rightLens.scale.z = 0.5;
199          glassesGroup.add(rightLens);
200  
201          // Frames
202          const frameGeo = new THREE.TorusGeometry(7, 0.5, 8, 32);
203          const leftFrame = new THREE.Mesh(frameGeo, frameMaterial);
204          leftFrame.position.set(-9, 42, 17);
205          glassesGroup.add(leftFrame);
206  
207          const rightFrame = new THREE.Mesh(frameGeo, frameMaterial);
208          rightFrame.position.set(9, 42, 17);
209          glassesGroup.add(rightFrame);
210  
211          // Bridge
212          const bridgeGeo = new THREE.CylinderGeometry(0.8, 0.8, 6, 8);
213          const bridge = new THREE.Mesh(bridgeGeo, frameMaterial);
214          bridge.rotation.z = Math.PI / 2;
215          bridge.position.set(0, 42, 17);
216          glassesGroup.add(bridge);
217  
218          // White reflections
219          const reflectGeo = new THREE.SphereGeometry(1.5, 16, 16);
220          const reflectMat = new THREE.MeshBasicMaterial({
221              color: 0xffffff,
222              transparent: true,
223              opacity: 0.7
224          });
225          const leftReflect = new THREE.Mesh(reflectGeo, reflectMat);
226          leftReflect.position.set(-11, 45, 20);
227          glassesGroup.add(leftReflect);
228  
229          const rightReflect = new THREE.Mesh(reflectGeo, reflectMat);
230          rightReflect.position.set(7, 45, 20);
231          glassesGroup.add(rightReflect);
232  
233          kamaji.add(glassesGroup);
234  
235          // === BEARD (MULTI-LAYER) ===
236          const beardGroup = new THREE.Group();
237  
238          // Center main mass
239          const beardCenterGeo = new THREE.SphereGeometry(10, 24, 24);
240          const beardCenter = new THREE.Mesh(beardCenterGeo, beardMaterial);
241          beardCenter.position.set(0, 30, 14);
242          beardCenter.scale.set(1.2, 1.6, 0.8);
243          beardCenter.castShadow = true;
244          beardGroup.add(beardCenter);
245  
246          // Left side
247          const beardLeftGeo = new THREE.SphereGeometry(7, 20, 20);
248          const beardLeft = new THREE.Mesh(beardLeftGeo, beardMaterial);
249          beardLeft.position.set(-9, 32, 13);
250          beardLeft.scale.set(1, 1.4, 0.8);
251          beardLeft.castShadow = true;
252          beardGroup.add(beardLeft);
253  
254          // Right side
255          const beardRight = new THREE.Mesh(beardLeftGeo, beardMaterial);
256          beardRight.position.set(9, 32, 13);
257          beardRight.scale.set(1, 1.4, 0.8);
258          beardRight.castShadow = true;
259          beardGroup.add(beardRight);
260  
261          // Lower layers for fullness
262          const beardLowerGeo = new THREE.SphereGeometry(6, 20, 20);
263          const beardLower1 = new THREE.Mesh(beardLowerGeo, beardMaterial);
264          beardLower1.position.set(-6, 24, 12);
265          beardLower1.scale.set(1, 1.5, 0.7);
266          beardLower1.castShadow = true;
267          beardGroup.add(beardLower1);
268  
269          const beardLower2 = new THREE.Mesh(beardLowerGeo, beardMaterial);
270          beardLower2.position.set(6, 24, 12);
271          beardLower2.scale.set(1, 1.5, 0.7);
272          beardLower2.castShadow = true;
273          beardGroup.add(beardLower2);
274  
275          // Bottom center
276          const beardBottom = new THREE.Mesh(beardLowerGeo, beardMaterial);
277          beardBottom.position.set(0, 22, 13);
278          beardBottom.scale.set(1, 1.3, 0.7);
279          beardBottom.castShadow = true;
280          beardGroup.add(beardBottom);
281  
282          kamaji.add(beardGroup);
283  
284          // === TORSO (BLUE SHIRT) ===
285          const torsoGeo = new THREE.CylinderGeometry(18, 24, 55, 24);
286          const torso = new THREE.Mesh(torsoGeo, shirtMaterial);
287          torso.position.y = 0;
288          torso.castShadow = true;
289          torso.receiveShadow = true;
290          kamaji.add(torso);
291  
292          // === FUNCTION: CREATE DETAILED ARM ===
293          function createDetailedArm() {
294              const armGroup = new THREE.Group();
295  
296              // Upper arm (blue sleeve)
297              const upperArmGeo = new THREE.CylinderGeometry(3.5, 3, 20, 16);
298              const upperArm = new THREE.Mesh(upperArmGeo, shirtMaterial);
299              upperArm.position.y = 8;
300              upperArm.castShadow = true;
301              armGroup.add(upperArm);
302  
303              // Lower arm (blue sleeve)
304              const lowerArmGeo = new THREE.CylinderGeometry(3, 2.8, 20, 16);
305              const lowerArm = new THREE.Mesh(lowerArmGeo, shirtMaterial);
306              lowerArm.position.y = -8;
307              lowerArm.castShadow = true;
308              armGroup.add(lowerArm);
309  
310              // Hand (tan skin)
311              const handGeo = new THREE.SphereGeometry(3.5, 16, 16);
312              const hand = new THREE.Mesh(handGeo, skinMaterial);
313              hand.position.y = -18;
314              hand.scale.set(1.1, 1.4, 0.85);
315              hand.castShadow = true;
316              armGroup.add(hand);
317  
318              // === FINGERS (5 DETAILED FINGERS) ===
319              const fingerPositions = [
320                  { x: -3, z: 1.5, scale: 0.85, name: 'pinky' },
321                  { x: -1.5, z: 2, scale: 0.95, name: 'ring' },
322                  { x: 0, z: 2.2, scale: 1.0, name: 'middle' },
323                  { x: 1.5, z: 2, scale: 0.95, name: 'index' },
324                  { x: 3, z: 1, scale: 0.8, name: 'thumb' }
325              ];
326  
327              fingerPositions.forEach((pos, i) => {
328                  const fingerGroup = new THREE.Group();
329  
330                  // Create 3 segments per finger
331                  for (let seg = 0; seg < 3; seg++) {
332                      const segmentGeo = new THREE.CylinderGeometry(
333                          0.4 * pos.scale,
334                          0.35 * pos.scale,
335                          1.8,
336                          8
337                      );
338                      const segment = new THREE.Mesh(segmentGeo, skinMaterial);
339                      segment.position.y = seg * 1.8;
340                      segment.castShadow = true;
341                      fingerGroup.add(segment);
342                  }
343  
344                  fingerGroup.position.set(pos.x, -21, pos.z);
345                  fingerGroup.rotation.x = Math.PI / 2;
346  
347                  // Thumb special rotation
348                  if (i === 4) {
349                      fingerGroup.rotation.z = -0.5;
350                      fingerGroup.rotation.y = -0.3;
351                  }
352  
353                  armGroup.add(fingerGroup);
354              });
355  
356              return armGroup;
357          }
358  
359          // === ADD 6 ARMS (3 PER SIDE) ===
360          const armConfigs = [
361              // Left side
362              { side: -1, yPos: 15, zPos: 0, rotZ: 0.8, rotY: -0.3 },
363              { side: -1, yPos: 5, zPos: 5, rotZ: 0.6, rotY: -0.2 },
364              { side: -1, yPos: -5, zPos: -5, rotZ: 0.4, rotY: -0.1 },
365              // Right side
366              { side: 1, yPos: 15, zPos: 0, rotZ: -0.8, rotY: 0.3 },
367              { side: 1, yPos: 5, zPos: 5, rotZ: -0.6, rotY: 0.2 },
368              { side: 1, yPos: -5, zPos: -5, rotZ: -0.4, rotY: 0.1 }
369          ];
370  
371          const arms = [];
372          armConfigs.forEach(config => {
373              const arm = createDetailedArm();
374              arm.position.set(config.side * 22, config.yPos, config.zPos);
375              arm.rotation.z = config.rotZ;
376              arm.rotation.y = config.rotY;
377              kamaji.add(arm);
378              arms.push(arm);
379          });
380  
381          // === FLOOR (FOR SHADOWS) ===
382          const floorGeo = new THREE.PlaneGeometry(200, 200);
383          const floorMat = new THREE.ShadowMaterial({ opacity: 0.3 });
384          const floor = new THREE.Mesh(floorGeo, floorMat);
385          floor.rotation.x = -Math.PI / 2;
386          floor.position.y = -30;
387          floor.receiveShadow = true;
388          scene.add(floor);
389  
390          scene.add(kamaji);
391  
392          // === ORBIT CONTROLS (PROFESSIONAL) ===
393          const controls = new OrbitControls(camera, renderer.domElement);
394          controls.enableDamping = true;
395          controls.dampingFactor = 0.05;
396          controls.target.set(0, 20, 0);
397          controls.minDistance = 80;
398          controls.maxDistance = 300;
399          controls.maxPolarAngle = Math.PI / 2;
400          controls.enablePan = true;
401  
402          // === ANIMATION LOOP ===
403          const clock = new THREE.Clock();
404  
405          function animate() {
406              requestAnimationFrame(animate);
407  
408              const time = clock.getElapsedTime();
409  
410              // Subtle breathing animation
411              const breathScale = 1 + Math.sin(time * 1.5) * 0.008;
412              torso.scale.set(1, breathScale, 1);
413  
414              // Gentle arm sway
415              arms.forEach((arm, i) => {
416                  const offset = i * 0.5;
417                  arm.rotation.x = Math.sin(time * 0.8 + offset) * 0.08;
418              });
419  
420              // Slight head bob
421              head.position.y = 40 + Math.sin(time * 1.5) * 0.3;
422  
423              controls.update();
424              renderer.render(scene, camera);
425          }
426  
427          animate();
428  
429          // === RESPONSIVE RESIZE ===
430          window.addEventListener('resize', () => {
431              const width = Math.min(window.innerWidth, 600);
432              const height = Math.min(window.innerHeight, 600);
433              camera.aspect = width / height;
434              camera.updateProjectionMatrix();
435              renderer.setSize(width, height);
436          });
437  
438      </script>
439  </body>
440  </html>