改为使用three.js 实现更好的 3D效果
Browse files- index.html +192 -165
index.html
CHANGED
|
@@ -11,7 +11,6 @@
|
|
| 11 |
<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.min.js"></script>
|
| 12 |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 13 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
| 14 |
-
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/jsm/controls/OrbitControls.js"></script>
|
| 15 |
<style>
|
| 16 |
@keyframes float {
|
| 17 |
0%, 100% { transform: translateY(0px); }
|
|
@@ -29,12 +28,13 @@
|
|
| 29 |
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 30 |
}
|
| 31 |
.perspective-3d {
|
| 32 |
-
perspective:
|
| 33 |
}
|
| 34 |
#threejs-container {
|
| 35 |
width: 100%;
|
| 36 |
height: 384px;
|
| 37 |
-
|
|
|
|
| 38 |
}
|
| 39 |
</style>
|
| 40 |
</head>
|
|
@@ -123,18 +123,16 @@
|
|
| 123 |
<button onclick="changeView('angle')" class="view-btn px-3 py-1 rounded-lg bg-gray-700 hover:bg-gray-600 transition-colors">Angle</button>
|
| 124 |
</div>
|
| 125 |
</div>
|
| 126 |
-
<div
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
<
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
<button onclick="setSwingStage('follow')" class="stage-btn px-3 py-1 rounded bg-gray-700 hover:bg-gray-600 text-sm">Follow</button>
|
| 137 |
-
</div>
|
| 138 |
</div>
|
| 139 |
</div>
|
| 140 |
|
|
@@ -398,211 +396,238 @@
|
|
| 398 |
}
|
| 399 |
}
|
| 400 |
});
|
| 401 |
-
// Three.js
|
| 402 |
-
let scene, camera, renderer,
|
| 403 |
-
let skeleton, golfClub;
|
| 404 |
let currentStage = 'address';
|
| 405 |
-
|
| 406 |
-
// Initialize Three.js
|
| 407 |
function initThreeJS() {
|
| 408 |
const container = document.getElementById('threejs-container');
|
|
|
|
|
|
|
| 409 |
|
| 410 |
// Scene
|
| 411 |
scene = new THREE.Scene();
|
| 412 |
-
scene.background =
|
| 413 |
-
scene.fog = new THREE.Fog(
|
| 414 |
|
| 415 |
// Camera
|
| 416 |
-
camera = new THREE.PerspectiveCamera(75,
|
| 417 |
-
camera.position.set(0,
|
|
|
|
| 418 |
|
| 419 |
// Renderer
|
| 420 |
-
renderer = new THREE.WebGLRenderer({ antialias: true
|
| 421 |
-
renderer.setSize(
|
| 422 |
renderer.shadowMap.enabled = true;
|
| 423 |
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
| 424 |
container.appendChild(renderer.domElement);
|
| 425 |
|
| 426 |
-
// Controls
|
| 427 |
-
controls = new THREE.OrbitControls(camera, renderer.domElement);
|
| 428 |
-
controls.enableDamping = true;
|
| 429 |
-
controls.dampingFactor = 0.05;
|
| 430 |
-
|
| 431 |
// Lighting
|
| 432 |
-
const ambientLight = new THREE.AmbientLight(0x404040,
|
| 433 |
scene.add(ambientLight);
|
| 434 |
|
| 435 |
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
| 436 |
-
directionalLight.position.set(
|
| 437 |
directionalLight.castShadow = true;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 438 |
scene.add(directionalLight);
|
| 439 |
|
| 440 |
-
const pointLight = new THREE.PointLight(0x4ade80,
|
| 441 |
-
pointLight.position.set(0,
|
| 442 |
scene.add(pointLight);
|
| 443 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 444 |
// Create Skeleton
|
| 445 |
createSkeleton();
|
| 446 |
|
| 447 |
-
//
|
| 448 |
-
|
| 449 |
|
| 450 |
-
//
|
| 451 |
animate();
|
|
|
|
|
|
|
|
|
|
| 452 |
}
|
| 453 |
|
| 454 |
function createSkeleton() {
|
| 455 |
skeleton = new THREE.Group();
|
| 456 |
|
| 457 |
-
//
|
| 458 |
-
const
|
| 459 |
-
|
| 460 |
-
color: 0x22c55e,
|
| 461 |
emissive: 0x22c55e,
|
| 462 |
-
emissiveIntensity: 0.3
|
|
|
|
| 463 |
});
|
| 464 |
|
| 465 |
-
//
|
| 466 |
-
const
|
| 467 |
-
|
| 468 |
-
const joint = new THREE.Mesh(jointGeometry, jointMaterial);
|
| 469 |
-
joint.position.set(0, 40 - i * 15, 0);
|
| 470 |
-
skeleton.add(joint);
|
| 471 |
-
spineJoints.push(joint);
|
| 472 |
-
}
|
| 473 |
-
|
| 474 |
-
// Shoulder joints
|
| 475 |
-
const leftShoulder = new THREE.Mesh(jointGeometry, jointMaterial);
|
| 476 |
-
leftShoulder.position.set(-15, 25, 0);
|
| 477 |
-
skeleton.add(leftShoulder);
|
| 478 |
-
|
| 479 |
-
const rightShoulder = new THREE.Mesh(jointGeometry, jointMaterial);
|
| 480 |
-
rightShoulder.position.set(15, 25, 0);
|
| 481 |
-
skeleton.add(rightShoulder);
|
| 482 |
-
|
| 483 |
-
// Bones (cylinders)
|
| 484 |
-
const boneMaterial = new THREE.MeshPhongMaterial({
|
| 485 |
-
color: 0x4ade80,
|
| 486 |
emissive: 0x4ade80,
|
| 487 |
-
emissiveIntensity: 0.
|
|
|
|
| 488 |
});
|
| 489 |
|
| 490 |
-
//
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
function createBone(start, end) {
|
| 510 |
-
const direction = new THREE.Vector3().subVectors(end, start);
|
| 511 |
-
const length = direction.length();
|
| 512 |
-
|
| 513 |
-
const boneGeometry = new THREE.CylinderGeometry(0.5, 0.5, length, 8);
|
| 514 |
-
const bone = new THREE.Mesh(boneGeometry, new THREE.MeshPhongMaterial({
|
| 515 |
-
color: 0x4ade80,
|
| 516 |
-
emissive: 0x4ade80,
|
| 517 |
-
emissiveIntensity: 0.1
|
| 518 |
});
|
| 519 |
|
| 520 |
-
//
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 524 |
|
| 525 |
-
|
| 526 |
}
|
| 527 |
|
| 528 |
function createGolfClub() {
|
| 529 |
golfClub = new THREE.Group();
|
| 530 |
|
| 531 |
// Club shaft
|
| 532 |
-
const shaftGeometry = new THREE.CylinderGeometry(0.
|
| 533 |
-
const shaftMaterial = new THREE.MeshPhongMaterial({
|
| 534 |
-
color:
|
|
|
|
|
|
|
|
|
|
| 535 |
});
|
| 536 |
const shaft = new THREE.Mesh(shaftGeometry, shaftMaterial);
|
| 537 |
-
shaft.position.set(
|
| 538 |
-
shaft.
|
| 539 |
-
|
| 540 |
|
| 541 |
// Club head
|
| 542 |
-
const headGeometry = new THREE.BoxGeometry(
|
| 543 |
-
const headMaterial = new THREE.MeshPhongMaterial({
|
| 544 |
-
color:
|
| 545 |
-
|
| 546 |
-
|
|
|
|
| 547 |
});
|
| 548 |
const head = new THREE.Mesh(headGeometry, headMaterial);
|
| 549 |
-
head.position.set(
|
| 550 |
-
|
|
|
|
| 551 |
|
| 552 |
-
|
| 553 |
-
golfClub.
|
| 554 |
-
|
| 555 |
-
|
| 556 |
-
skeleton.add(golfClub);
|
| 557 |
-
}
|
| 558 |
-
|
| 559 |
-
function onWindowResize() {
|
| 560 |
-
const container = document.getElementById('threejs-container');
|
| 561 |
-
camera.aspect = container.clientWidth / container.clientHeight;
|
| 562 |
-
camera.updateProjectionMatrix();
|
| 563 |
-
renderer.setSize(container.clientWidth, container.clientHeight);
|
| 564 |
}
|
| 565 |
|
| 566 |
function animate() {
|
| 567 |
requestAnimationFrame(animate);
|
| 568 |
-
controls.update();
|
| 569 |
|
| 570 |
-
//
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
}
|
| 576 |
-
});
|
| 577 |
|
| 578 |
renderer.render(scene, camera);
|
| 579 |
}
|
| 580 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 581 |
// 3D Model View Change
|
| 582 |
function changeView(view) {
|
| 583 |
const buttons = document.querySelectorAll('.view-btn');
|
| 584 |
-
|
| 585 |
buttons.forEach(btn => btn.classList.remove('bg-green-600'));
|
| 586 |
event.target.classList.add('bg-green-600');
|
| 587 |
|
| 588 |
switch(view) {
|
| 589 |
case 'front':
|
| 590 |
-
camera.position.set(0,
|
| 591 |
-
camera.lookAt(0, 0, 0);
|
| 592 |
break;
|
| 593 |
case 'side':
|
| 594 |
-
camera.position.set(
|
| 595 |
-
camera.lookAt(0, 0, 0);
|
| 596 |
break;
|
| 597 |
case 'top':
|
| 598 |
-
camera.position.set(0,
|
| 599 |
-
camera.lookAt(0, 0, 0);
|
| 600 |
break;
|
| 601 |
case 'angle':
|
| 602 |
-
camera.position.set(
|
| 603 |
-
camera.lookAt(0, 0, 0);
|
| 604 |
break;
|
| 605 |
}
|
|
|
|
| 606 |
}
|
| 607 |
|
| 608 |
// Swing Stage Control
|
|
@@ -617,28 +642,32 @@
|
|
| 617 |
|
| 618 |
currentStage = stage;
|
| 619 |
|
| 620 |
-
// Animate
|
| 621 |
-
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 642 |
}
|
| 643 |
}
|
| 644 |
// Video Controls
|
|
@@ -654,6 +683,7 @@
|
|
| 654 |
}
|
| 655 |
// Add animations on load
|
| 656 |
window.addEventListener('load', () => {
|
|
|
|
| 657 |
anime({
|
| 658 |
targets: '.glass-effect',
|
| 659 |
translateY: [50, 0],
|
|
@@ -662,9 +692,6 @@
|
|
| 662 |
duration: 800,
|
| 663 |
easing: 'easeOutQuad'
|
| 664 |
});
|
| 665 |
-
|
| 666 |
-
// Initialize Three.js after page load
|
| 667 |
-
initThreeJS();
|
| 668 |
});
|
| 669 |
</script>
|
| 670 |
</body>
|
|
|
|
| 11 |
<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.min.js"></script>
|
| 12 |
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
| 13 |
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
|
|
| 14 |
<style>
|
| 15 |
@keyframes float {
|
| 16 |
0%, 100% { transform: translateY(0px); }
|
|
|
|
| 28 |
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 29 |
}
|
| 30 |
.perspective-3d {
|
| 31 |
+
perspective: 1000px;
|
| 32 |
}
|
| 33 |
#threejs-container {
|
| 34 |
width: 100%;
|
| 35 |
height: 384px;
|
| 36 |
+
border-radius: 0.75rem;
|
| 37 |
+
overflow: hidden;
|
| 38 |
}
|
| 39 |
</style>
|
| 40 |
</head>
|
|
|
|
| 123 |
<button onclick="changeView('angle')" class="view-btn px-3 py-1 rounded-lg bg-gray-700 hover:bg-gray-600 transition-colors">Angle</button>
|
| 124 |
</div>
|
| 125 |
</div>
|
| 126 |
+
<div id="threejs-container"></div>
|
| 127 |
+
|
| 128 |
+
<!-- Stage Indicators -->
|
| 129 |
+
<div class="mt-4 flex space-x-2">
|
| 130 |
+
<button onclick="setSwingStage('address')" class="stage-btn px-3 py-1 rounded bg-green-600 text-white text-sm">Address</button>
|
| 131 |
+
<button onclick="setSwingStage('backswing')" class="stage-btn px-3 py-1 rounded bg-gray-700 hover:bg-gray-600 text-sm">Backswing</button>
|
| 132 |
+
<button onclick="setSwingStage('top')" class="stage-btn px-3 py-1 rounded bg-gray-700 hover:bg-gray-600 text-sm">Top</button>
|
| 133 |
+
<button onclick="setSwingStage('downswing')" class="stage-btn px-3 py-1 rounded bg-gray-700 hover:bg-gray-600 text-sm">Downswing</button>
|
| 134 |
+
<button onclick="setSwingStage('impact')" class="stage-btn px-3 py-1 rounded bg-gray-700 hover:bg-gray-600 text-sm">Impact</button>
|
| 135 |
+
<button onclick="setSwingStage('follow')" class="stage-btn px-3 py-1 rounded bg-gray-700 hover:bg-gray-600 text-sm">Follow</button>
|
|
|
|
|
|
|
| 136 |
</div>
|
| 137 |
</div>
|
| 138 |
|
|
|
|
| 396 |
}
|
| 397 |
}
|
| 398 |
});
|
| 399 |
+
// Three.js Setup
|
| 400 |
+
let scene, camera, renderer, skeleton, golfClub;
|
|
|
|
| 401 |
let currentStage = 'address';
|
| 402 |
+
|
|
|
|
| 403 |
function initThreeJS() {
|
| 404 |
const container = document.getElementById('threejs-container');
|
| 405 |
+
const width = container.clientWidth;
|
| 406 |
+
const height = container.clientHeight;
|
| 407 |
|
| 408 |
// Scene
|
| 409 |
scene = new THREE.Scene();
|
| 410 |
+
scene.background = new THREE.Color(0x1a1a2e);
|
| 411 |
+
scene.fog = new THREE.Fog(0x1a1a2e, 1, 1000);
|
| 412 |
|
| 413 |
// Camera
|
| 414 |
+
camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
|
| 415 |
+
camera.position.set(0, 5, 15);
|
| 416 |
+
camera.lookAt(0, 5, 0);
|
| 417 |
|
| 418 |
// Renderer
|
| 419 |
+
renderer = new THREE.WebGLRenderer({ antialias: true });
|
| 420 |
+
renderer.setSize(width, height);
|
| 421 |
renderer.shadowMap.enabled = true;
|
| 422 |
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
| 423 |
container.appendChild(renderer.domElement);
|
| 424 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 425 |
// Lighting
|
| 426 |
+
const ambientLight = new THREE.AmbientLight(0x404040, 2);
|
| 427 |
scene.add(ambientLight);
|
| 428 |
|
| 429 |
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
| 430 |
+
directionalLight.position.set(5, 10, 5);
|
| 431 |
directionalLight.castShadow = true;
|
| 432 |
+
directionalLight.shadow.camera.near = 0.1;
|
| 433 |
+
directionalLight.shadow.camera.far = 50;
|
| 434 |
+
directionalLight.shadow.camera.left = -10;
|
| 435 |
+
directionalLight.shadow.camera.right = 10;
|
| 436 |
+
directionalLight.shadow.camera.top = 10;
|
| 437 |
+
directionalLight.shadow.camera.bottom = -10;
|
| 438 |
scene.add(directionalLight);
|
| 439 |
|
| 440 |
+
const pointLight = new THREE.PointLight(0x4ade80, 1, 100);
|
| 441 |
+
pointLight.position.set(0, 8, 0);
|
| 442 |
scene.add(pointLight);
|
| 443 |
|
| 444 |
+
// Ground
|
| 445 |
+
const groundGeometry = new THREE.PlaneGeometry(50, 50);
|
| 446 |
+
const groundMaterial = new THREE.MeshStandardMaterial({
|
| 447 |
+
color: 0x2a2a3e,
|
| 448 |
+
roughness: 0.8,
|
| 449 |
+
metalness: 0.2
|
| 450 |
+
});
|
| 451 |
+
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
|
| 452 |
+
ground.rotation.x = -Math.PI / 2;
|
| 453 |
+
ground.receiveShadow = true;
|
| 454 |
+
scene.add(ground);
|
| 455 |
+
|
| 456 |
// Create Skeleton
|
| 457 |
createSkeleton();
|
| 458 |
|
| 459 |
+
// Create Golf Club
|
| 460 |
+
createGolfClub();
|
| 461 |
|
| 462 |
+
// Animation Loop
|
| 463 |
animate();
|
| 464 |
+
|
| 465 |
+
// Handle Resize
|
| 466 |
+
window.addEventListener('resize', onWindowResize);
|
| 467 |
}
|
| 468 |
|
| 469 |
function createSkeleton() {
|
| 470 |
skeleton = new THREE.Group();
|
| 471 |
|
| 472 |
+
// Joint material
|
| 473 |
+
const jointMaterial = new THREE.MeshPhongMaterial({
|
| 474 |
+
color: 0x4ade80,
|
|
|
|
| 475 |
emissive: 0x22c55e,
|
| 476 |
+
emissiveIntensity: 0.3,
|
| 477 |
+
shininess: 100
|
| 478 |
});
|
| 479 |
|
| 480 |
+
// Bone material
|
| 481 |
+
const boneMaterial = new THREE.MeshPhongMaterial({
|
| 482 |
+
color: 0x22c55e,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
emissive: 0x4ade80,
|
| 484 |
+
emissiveIntensity: 0.1,
|
| 485 |
+
shininess: 50
|
| 486 |
});
|
| 487 |
|
| 488 |
+
// Create joints and bones
|
| 489 |
+
const joints = [
|
| 490 |
+
{ x: 0, y: 8, z: 0 }, // Head
|
| 491 |
+
{ x: 0, y: 6, z: 0 }, // Neck
|
| 492 |
+
{ x: 0, y: 4, z: 0 }, // Chest
|
| 493 |
+
{ x: 0, y: 2, z: 0 }, // Waist
|
| 494 |
+
{ x: 0, y: 0, z: 0 }, // Hips
|
| 495 |
+
{ x: -1.5, y: 6, z: 0 }, // Left Shoulder
|
| 496 |
+
{ x: 1.5, y: 6, z: 0 } // Right Shoulder
|
| 497 |
+
];
|
| 498 |
+
|
| 499 |
+
// Add joints
|
| 500 |
+
joints.forEach(pos => {
|
| 501 |
+
const jointGeometry = new THREE.SphereGeometry(0.2, 16, 16);
|
| 502 |
+
const joint = new THREE.Mesh(jointGeometry, jointMaterial);
|
| 503 |
+
joint.position.set(pos.x, pos.y, pos.z);
|
| 504 |
+
joint.castShadow = true;
|
| 505 |
+
skeleton.add(joint);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
});
|
| 507 |
|
| 508 |
+
// Add bones
|
| 509 |
+
const bones = [
|
| 510 |
+
{ start: 0, end: 1 }, // Head to Neck
|
| 511 |
+
{ start: 1, end: 2 }, // Neck to Chest
|
| 512 |
+
{ start: 2, end: 3 }, // Chest to Waist
|
| 513 |
+
{ start: 3, end: 4 }, // Waist to Hips
|
| 514 |
+
{ start: 1, end: 5 }, // Neck to Left Shoulder
|
| 515 |
+
{ start: 1, end: 6 } // Neck to Right Shoulder
|
| 516 |
+
];
|
| 517 |
+
|
| 518 |
+
bones.forEach(bone => {
|
| 519 |
+
const start = joints[bone.start];
|
| 520 |
+
const end = joints[bone.end];
|
| 521 |
+
const length = Math.sqrt(
|
| 522 |
+
Math.pow(end.x - start.x, 2) +
|
| 523 |
+
Math.pow(end.y - start.y, 2) +
|
| 524 |
+
Math.pow(end.z - start.z, 2)
|
| 525 |
+
);
|
| 526 |
+
|
| 527 |
+
const boneGeometry = new THREE.CylinderGeometry(0.1, 0.1, length, 8);
|
| 528 |
+
const boneMesh = new THREE.Mesh(boneGeometry, boneMaterial);
|
| 529 |
+
|
| 530 |
+
// Position and rotate bone
|
| 531 |
+
const midX = (start.x + end.x) / 2;
|
| 532 |
+
const midY = (start.y + end.y) / 2;
|
| 533 |
+
const midZ = (start.z + end.z) / 2;
|
| 534 |
+
boneMesh.position.set(midX, midY, midZ);
|
| 535 |
+
|
| 536 |
+
// Calculate rotation
|
| 537 |
+
const direction = new THREE.Vector3(
|
| 538 |
+
end.x - start.x,
|
| 539 |
+
end.y - start.y,
|
| 540 |
+
end.z - start.z
|
| 541 |
+
).normalize();
|
| 542 |
+
boneMesh.lookAt(
|
| 543 |
+
new THREE.Vector3(midX, midY, midZ).add(direction)
|
| 544 |
+
);
|
| 545 |
+
boneMesh.rotateX(Math.PI / 2);
|
| 546 |
+
|
| 547 |
+
boneMesh.castShadow = true;
|
| 548 |
+
skeleton.add(boneMesh);
|
| 549 |
+
});
|
| 550 |
|
| 551 |
+
scene.add(skeleton);
|
| 552 |
}
|
| 553 |
|
| 554 |
function createGolfClub() {
|
| 555 |
golfClub = new THREE.Group();
|
| 556 |
|
| 557 |
// Club shaft
|
| 558 |
+
const shaftGeometry = new THREE.CylinderGeometry(0.05, 0.05, 6, 8);
|
| 559 |
+
const shaftMaterial = new THREE.MeshPhongMaterial({
|
| 560 |
+
color: 0x94a3b8,
|
| 561 |
+
emissive: 0x475569,
|
| 562 |
+
emissiveIntensity: 0.1,
|
| 563 |
+
shininess: 100
|
| 564 |
});
|
| 565 |
const shaft = new THREE.Mesh(shaftGeometry, shaftMaterial);
|
| 566 |
+
shaft.position.set(2, 6, 0);
|
| 567 |
+
shaft.rotation.z = Math.PI / 6;
|
| 568 |
+
shaft.castShadow = true;
|
| 569 |
|
| 570 |
// Club head
|
| 571 |
+
const headGeometry = new THREE.BoxGeometry(0.4, 0.3, 0.2);
|
| 572 |
+
const headMaterial = new THREE.MeshPhongMaterial({
|
| 573 |
+
color: 0xfbbf24,
|
| 574 |
+
emissive: 0xf59e0b,
|
| 575 |
+
emissiveIntensity: 0.2,
|
| 576 |
+
shininess: 150
|
| 577 |
});
|
| 578 |
const head = new THREE.Mesh(headGeometry, headMaterial);
|
| 579 |
+
head.position.set(2, 3.2, 0);
|
| 580 |
+
head.rotation.z = Math.PI / 6;
|
| 581 |
+
head.castShadow = true;
|
| 582 |
|
| 583 |
+
golfClub.add(shaft);
|
| 584 |
+
golfClub.add(head);
|
| 585 |
+
scene.add(golfClub);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 586 |
}
|
| 587 |
|
| 588 |
function animate() {
|
| 589 |
requestAnimationFrame(animate);
|
|
|
|
| 590 |
|
| 591 |
+
// Rotate camera around skeleton
|
| 592 |
+
const time = Date.now() * 0.0005;
|
| 593 |
+
camera.position.x = Math.cos(time) * 15;
|
| 594 |
+
camera.position.z = Math.sin(time) * 15;
|
| 595 |
+
camera.lookAt(0, 5, 0);
|
|
|
|
|
|
|
| 596 |
|
| 597 |
renderer.render(scene, camera);
|
| 598 |
}
|
| 599 |
|
| 600 |
+
function onWindowResize() {
|
| 601 |
+
const container = document.getElementById('threejs-container');
|
| 602 |
+
const width = container.clientWidth;
|
| 603 |
+
const height = container.clientHeight;
|
| 604 |
+
|
| 605 |
+
camera.aspect = width / height;
|
| 606 |
+
camera.updateProjectionMatrix();
|
| 607 |
+
renderer.setSize(width, height);
|
| 608 |
+
}
|
| 609 |
+
|
| 610 |
// 3D Model View Change
|
| 611 |
function changeView(view) {
|
| 612 |
const buttons = document.querySelectorAll('.view-btn');
|
|
|
|
| 613 |
buttons.forEach(btn => btn.classList.remove('bg-green-600'));
|
| 614 |
event.target.classList.add('bg-green-600');
|
| 615 |
|
| 616 |
switch(view) {
|
| 617 |
case 'front':
|
| 618 |
+
camera.position.set(0, 5, 15);
|
|
|
|
| 619 |
break;
|
| 620 |
case 'side':
|
| 621 |
+
camera.position.set(15, 5, 0);
|
|
|
|
| 622 |
break;
|
| 623 |
case 'top':
|
| 624 |
+
camera.position.set(0, 20, 0);
|
|
|
|
| 625 |
break;
|
| 626 |
case 'angle':
|
| 627 |
+
camera.position.set(10, 10, 10);
|
|
|
|
| 628 |
break;
|
| 629 |
}
|
| 630 |
+
camera.lookAt(0, 5, 0);
|
| 631 |
}
|
| 632 |
|
| 633 |
// Swing Stage Control
|
|
|
|
| 642 |
|
| 643 |
currentStage = stage;
|
| 644 |
|
| 645 |
+
// Animate golf club based on stage
|
| 646 |
+
switch(stage) {
|
| 647 |
+
case 'address':
|
| 648 |
+
golfClub.rotation.z = Math.PI / 6;
|
| 649 |
+
golfClub.position.set(0, 0, 0);
|
| 650 |
+
break;
|
| 651 |
+
case 'backswing':
|
| 652 |
+
golfClub.rotation.z = -Math.PI / 3;
|
| 653 |
+
golfClub.position.set(-1, 1, 0);
|
| 654 |
+
break;
|
| 655 |
+
case 'top':
|
| 656 |
+
golfClub.rotation.z = -Math.PI / 2;
|
| 657 |
+
golfClub.position.set(-2, 2, 0);
|
| 658 |
+
break;
|
| 659 |
+
case 'downswing':
|
| 660 |
+
golfClub.rotation.z = Math.PI / 4;
|
| 661 |
+
golfClub.position.set(1, 0.5, 0);
|
| 662 |
+
break;
|
| 663 |
+
case 'impact':
|
| 664 |
+
golfClub.rotation.z = Math.PI / 8;
|
| 665 |
+
golfClub.position.set(2, 0, 0);
|
| 666 |
+
break;
|
| 667 |
+
case 'follow':
|
| 668 |
+
golfClub.rotation.z = Math.PI / 2;
|
| 669 |
+
golfClub.position.set(2, 3, 0);
|
| 670 |
+
break;
|
| 671 |
}
|
| 672 |
}
|
| 673 |
// Video Controls
|
|
|
|
| 683 |
}
|
| 684 |
// Add animations on load
|
| 685 |
window.addEventListener('load', () => {
|
| 686 |
+
initThreeJS();
|
| 687 |
anime({
|
| 688 |
targets: '.glass-effect',
|
| 689 |
translateY: [50, 0],
|
|
|
|
| 692 |
duration: 800,
|
| 693 |
easing: 'easeOutQuad'
|
| 694 |
});
|
|
|
|
|
|
|
|
|
|
| 695 |
});
|
| 696 |
</script>
|
| 697 |
</body>
|