@@ -578,109 +578,198 @@ export function createBootHouse(gradientMap) { |
| 578 | 578 | // Sprite body colors |
| 579 | 579 | const spriteColors = [0xff6b9d, 0x9b59b6, 0x3498db, 0x2ecc71] |
| 580 | 580 | |
| 581 | | - // === BOOT STRUCTURE === |
| 582 | | - |
| 583 | | - // Boot sole (the base) |
| 584 | | - const soleGeom = new THREE.BoxGeometry(1.0, 0.12, 0.6) |
| 585 | | - soleGeom.translate(0.1, 0, 0) // Offset for toe curve |
| 586 | | - const sole = new THREE.Mesh(soleGeom, soleMaterial) |
| 587 | | - sole.position.y = 0.06 |
| 588 | | - group.add(sole) |
| 589 | | - |
| 590 | | - // Toe section - curved front of boot (rounded box) |
| 591 | | - const toeGeom = new THREE.SphereGeometry(0.35, 10, 8) |
| 592 | | - toeGeom.scale(1.2, 0.7, 1.0) |
| 593 | | - const toe = new THREE.Mesh(toeGeom, leatherMaterial) |
| 594 | | - toe.position.set(-0.35, 0.25, 0) |
| 595 | | - group.add(toe) |
| 596 | | - |
| 597 | | - // Main boot body (the ankle/shaft part) - this is the house |
| 598 | | - const shaftGeom = new THREE.CylinderGeometry(0.32, 0.38, 0.9, 10) |
| 581 | + // === BOOT STRUCTURE (Continuous realistic boot shape) === |
| 582 | + |
| 583 | + // The boot is built as overlapping shapes with the same material |
| 584 | + // to create a seamless, continuous appearance |
| 585 | + |
| 586 | + // --- SOLE (follows the boot footprint) --- |
| 587 | + // Main sole - elongated with rounded ends |
| 588 | + const soleLength = 1.1 |
| 589 | + const soleWidth = 0.5 |
| 590 | + const soleHeight = 0.1 |
| 591 | + |
| 592 | + // Sole base |
| 593 | + const soleBaseGeom = new THREE.BoxGeometry(soleLength - 0.3, soleHeight, soleWidth - 0.1) |
| 594 | + const soleBase = new THREE.Mesh(soleBaseGeom, soleMaterial) |
| 595 | + soleBase.position.set(0, soleHeight / 2, 0) |
| 596 | + group.add(soleBase) |
| 597 | + |
| 598 | + // Sole toe cap (rounded front) |
| 599 | + const soleToeGeom = new THREE.CylinderGeometry(soleWidth / 2 - 0.05, soleWidth / 2 - 0.05, soleHeight, 12) |
| 600 | + soleToeGeom.rotateX(Math.PI / 2) |
| 601 | + const soleToe = new THREE.Mesh(soleToeGeom, soleMaterial) |
| 602 | + soleToe.position.set(-soleLength / 2 + 0.15, soleHeight / 2, 0) |
| 603 | + group.add(soleToe) |
| 604 | + |
| 605 | + // Sole heel cap (rounded back) |
| 606 | + const soleHeelGeom = new THREE.CylinderGeometry(soleWidth / 2 - 0.05, soleWidth / 2 - 0.05, soleHeight * 1.3, 12) |
| 607 | + soleHeelGeom.rotateX(Math.PI / 2) |
| 608 | + const soleHeel = new THREE.Mesh(soleHeelGeom, soleMaterial) |
| 609 | + soleHeel.position.set(soleLength / 2 - 0.2, soleHeight * 0.65, 0) |
| 610 | + group.add(soleHeel) |
| 611 | + |
| 612 | + // --- FOOT PORTION (continuous with ankle) --- |
| 613 | + |
| 614 | + // Lower foot - the part that sits on the sole |
| 615 | + // Uses a squashed capsule shape tilted up at the toe |
| 616 | + const footBaseGeom = new THREE.CapsuleGeometry(0.22, 0.5, 8, 12) |
| 617 | + footBaseGeom.rotateZ(Math.PI / 2) // Lay it horizontal |
| 618 | + footBaseGeom.scale(1, 0.75, 1) // Flatten slightly |
| 619 | + const footBase = new THREE.Mesh(footBaseGeom, leatherMaterial) |
| 620 | + footBase.position.set(-0.1, 0.28, 0) |
| 621 | + footBase.rotation.z = -0.15 // Tilt toe up slightly |
| 622 | + group.add(footBase) |
| 623 | + |
| 624 | + // Toe box - rounded front that curves upward |
| 625 | + const toeBoxGeom = new THREE.SphereGeometry(0.28, 12, 10) |
| 626 | + toeBoxGeom.scale(0.9, 0.85, 1.0) |
| 627 | + const toeBox = new THREE.Mesh(toeBoxGeom, leatherMaterial) |
| 628 | + toeBox.position.set(-0.48, 0.26, 0) |
| 629 | + group.add(toeBox) |
| 630 | + |
| 631 | + // --- ANKLE/SHAFT (rises from heel, connects to foot) --- |
| 632 | + |
| 633 | + // Ankle transition - connects foot to shaft smoothly |
| 634 | + const ankleTransitionGeom = new THREE.SphereGeometry(0.34, 12, 10) |
| 635 | + ankleTransitionGeom.scale(1.0, 0.8, 1.0) |
| 636 | + const ankleTransition = new THREE.Mesh(ankleTransitionGeom, leatherMaterial) |
| 637 | + ankleTransition.position.set(0.25, 0.35, 0) |
| 638 | + group.add(ankleTransition) |
| 639 | + |
| 640 | + // Main shaft (ankle part) - tapers slightly upward |
| 641 | + const shaftGeom = new THREE.CylinderGeometry(0.28, 0.34, 0.75, 14) |
| 599 | 642 | const shaft = new THREE.Mesh(shaftGeom, leatherMaterial) |
| 600 | | - shaft.position.set(0.25, 0.6, 0) |
| 643 | + shaft.position.set(0.25, 0.72, 0) |
| 601 | 644 | group.add(shaft) |
| 602 | 645 | |
| 603 | | - // Boot rim/collar at top |
| 604 | | - const rimGeom = new THREE.TorusGeometry(0.34, 0.05, 6, 16) |
| 605 | | - const rim = new THREE.Mesh(rimGeom, darkLeatherMaterial) |
| 606 | | - rim.position.set(0.25, 1.05, 0) |
| 607 | | - rim.rotation.x = Math.PI / 2 |
| 608 | | - group.add(rim) |
| 609 | | - |
| 610 | | - // Tongue (extending from shaft toward toe) |
| 611 | | - const tongueGeom = new THREE.BoxGeometry(0.18, 0.4, 0.08) |
| 646 | + // Shaft top rim |
| 647 | + const shaftTopGeom = new THREE.TorusGeometry(0.30, 0.04, 8, 20) |
| 648 | + const shaftTop = new THREE.Mesh(shaftTopGeom, leatherMaterial) |
| 649 | + shaftTop.position.set(0.25, 1.1, 0) |
| 650 | + shaftTop.rotation.x = Math.PI / 2 |
| 651 | + group.add(shaftTop) |
| 652 | + |
| 653 | + // --- INSTEP/VAMP (connects toe to ankle on top) --- |
| 654 | + |
| 655 | + // This fills the gap between toe and shaft on top of the foot |
| 656 | + const vampGeom = new THREE.CapsuleGeometry(0.18, 0.35, 6, 10) |
| 657 | + vampGeom.rotateZ(Math.PI / 2) |
| 658 | + vampGeom.scale(1, 0.7, 0.9) |
| 659 | + const vamp = new THREE.Mesh(vampGeom, leatherMaterial) |
| 660 | + vamp.position.set(-0.08, 0.38, 0) |
| 661 | + vamp.rotation.z = -0.4 // Angle down toward toe |
| 662 | + group.add(vamp) |
| 663 | + |
| 664 | + // --- HEEL COUNTER (back of heel, reinforcement) --- |
| 665 | + const heelCounterGeom = new THREE.CylinderGeometry(0.26, 0.3, 0.25, 14, 1, false, -Math.PI * 0.6, Math.PI * 1.2) |
| 666 | + const heelCounter = new THREE.Mesh(heelCounterGeom, darkLeatherMaterial) |
| 667 | + heelCounter.position.set(0.35, 0.22, 0) |
| 668 | + group.add(heelCounter) |
| 669 | + |
| 670 | + // --- TONGUE --- |
| 671 | + const tongueGeom = new THREE.CapsuleGeometry(0.08, 0.25, 4, 8) |
| 612 | 672 | const tongue = new THREE.Mesh(tongueGeom, laceMaterial) |
| 613 | | - tongue.position.set(0.05, 0.5, 0.32) |
| 614 | | - tongue.rotation.x = 0.3 |
| 673 | + tongue.position.set(0.0, 0.55, 0.26) |
| 674 | + tongue.rotation.x = 0.35 |
| 675 | + tongue.rotation.z = -0.1 |
| 615 | 676 | group.add(tongue) |
| 616 | 677 | |
| 617 | | - // Lace holes (decorative dots) |
| 618 | | - const holeGeom = new THREE.CylinderGeometry(0.025, 0.025, 0.02, 8) |
| 619 | | - for (let i = 0; i < 4; i++) { |
| 620 | | - const holeL = new THREE.Mesh(holeGeom, soleMaterial) |
| 621 | | - holeL.position.set(-0.05, 0.35 + i * 0.15, 0.36) |
| 622 | | - holeL.rotation.x = Math.PI / 2 |
| 623 | | - group.add(holeL) |
| 624 | | - |
| 625 | | - const holeR = new THREE.Mesh(holeGeom, soleMaterial) |
| 626 | | - holeR.position.set(0.15, 0.35 + i * 0.15, 0.36) |
| 627 | | - holeR.rotation.x = Math.PI / 2 |
| 628 | | - group.add(holeR) |
| 629 | | - } |
| 630 | | - |
| 631 | | - // Cross-laces between holes |
| 678 | + // --- LACING DETAILS --- |
| 632 | 679 | const laceCordMaterial = new THREE.MeshToonMaterial({ |
| 633 | 680 | color: 0xf5deb3, |
| 634 | 681 | gradientMap |
| 635 | 682 | }) |
| 683 | + |
| 684 | + // Lace eyelets (holes) |
| 685 | + const eyeletGeom = new THREE.TorusGeometry(0.025, 0.008, 6, 12) |
| 686 | + for (let i = 0; i < 4; i++) { |
| 687 | + const y = 0.42 + i * 0.12 |
| 688 | + const z = 0.28 - i * 0.02 |
| 689 | + |
| 690 | + const eyeletL = new THREE.Mesh(eyeletGeom, darkLeatherMaterial) |
| 691 | + eyeletL.position.set(-0.06, y, z) |
| 692 | + eyeletL.rotation.x = Math.PI / 2 - 0.3 |
| 693 | + group.add(eyeletL) |
| 694 | + |
| 695 | + const eyeletR = new THREE.Mesh(eyeletGeom, darkLeatherMaterial) |
| 696 | + eyeletR.position.set(0.08, y, z) |
| 697 | + eyeletR.rotation.x = Math.PI / 2 - 0.3 |
| 698 | + group.add(eyeletR) |
| 699 | + } |
| 700 | + |
| 701 | + // Cross-laces |
| 636 | 702 | for (let i = 0; i < 3; i++) { |
| 637 | | - const cordGeom = new THREE.CylinderGeometry(0.01, 0.01, 0.22, 4) |
| 703 | + const y = 0.48 + i * 0.12 |
| 704 | + const z = 0.30 - i * 0.02 |
| 705 | + const cordGeom = new THREE.CylinderGeometry(0.008, 0.008, 0.16, 4) |
| 638 | 706 | const cord = new THREE.Mesh(cordGeom, laceCordMaterial) |
| 639 | | - cord.position.set(0.05, 0.42 + i * 0.15, 0.37) |
| 707 | + cord.position.set(0.01, y, z) |
| 640 | 708 | cord.rotation.z = Math.PI / 2 |
| 641 | | - cord.rotation.x = 0.3 |
| 709 | + cord.rotation.y = 0.3 |
| 642 | 710 | group.add(cord) |
| 643 | 711 | } |
| 644 | 712 | |
| 645 | | - // Heel bump |
| 646 | | - const heelGeom = new THREE.SphereGeometry(0.18, 8, 6) |
| 647 | | - heelGeom.scale(1.0, 0.8, 0.6) |
| 648 | | - const heel = new THREE.Mesh(heelGeom, soleMaterial) |
| 649 | | - heel.position.set(0.55, 0.1, 0) |
| 650 | | - group.add(heel) |
| 713 | + // Bow at top of lacing |
| 714 | + const bowLoopGeom = new THREE.TorusGeometry(0.04, 0.012, 6, 12, Math.PI) |
| 715 | + const bowL = new THREE.Mesh(bowLoopGeom, laceCordMaterial) |
| 716 | + bowL.position.set(-0.04, 0.82, 0.24) |
| 717 | + bowL.rotation.y = Math.PI / 2 |
| 718 | + bowL.rotation.x = 0.5 |
| 719 | + group.add(bowL) |
| 720 | + |
| 721 | + const bowR = new THREE.Mesh(bowLoopGeom, laceCordMaterial) |
| 722 | + bowR.position.set(0.06, 0.82, 0.24) |
| 723 | + bowR.rotation.y = -Math.PI / 2 |
| 724 | + bowR.rotation.x = 0.5 |
| 725 | + group.add(bowR) |
| 726 | + |
| 727 | + // Bow tails |
| 728 | + const tailGeom = new THREE.CylinderGeometry(0.01, 0.006, 0.12, 4) |
| 729 | + const tailL = new THREE.Mesh(tailGeom, laceCordMaterial) |
| 730 | + tailL.position.set(-0.06, 0.76, 0.26) |
| 731 | + tailL.rotation.z = 0.4 |
| 732 | + tailL.rotation.x = 0.3 |
| 733 | + group.add(tailL) |
| 734 | + |
| 735 | + const tailR = new THREE.Mesh(tailGeom, laceCordMaterial) |
| 736 | + tailR.position.set(0.08, 0.76, 0.26) |
| 737 | + tailR.rotation.z = -0.4 |
| 738 | + tailR.rotation.x = 0.3 |
| 739 | + group.add(tailR) |
| 651 | 740 | |
| 652 | 741 | // === WINDOWS === |
| 653 | 742 | |
| 654 | 743 | // Round window on toe |
| 655 | | - const toeWindowGeom = new THREE.CircleGeometry(0.08, 10) |
| 744 | + const toeWindowGeom = new THREE.CircleGeometry(0.07, 12) |
| 656 | 745 | const toeWindow = new THREE.Mesh(toeWindowGeom, windowMaterial) |
| 657 | | - toeWindow.position.set(-0.58, 0.3, 0) |
| 746 | + toeWindow.position.set(-0.72, 0.28, 0) |
| 658 | 747 | toeWindow.rotation.y = -Math.PI / 2 |
| 659 | 748 | group.add(toeWindow) |
| 660 | 749 | |
| 661 | 750 | // Window frame |
| 662 | | - const toeFrameGeom = new THREE.TorusGeometry(0.08, 0.015, 4, 16) |
| 751 | + const toeFrameGeom = new THREE.TorusGeometry(0.07, 0.012, 4, 16) |
| 663 | 752 | const toeFrame = new THREE.Mesh(toeFrameGeom, darkLeatherMaterial) |
| 664 | | - toeFrame.position.set(-0.57, 0.3, 0) |
| 753 | + toeFrame.position.set(-0.71, 0.28, 0) |
| 665 | 754 | toeFrame.rotation.y = -Math.PI / 2 |
| 666 | 755 | group.add(toeFrame) |
| 667 | 756 | |
| 668 | | - // Windows on shaft (2 small ones) |
| 757 | + // Windows on shaft (2 on sides) |
| 669 | 758 | for (let i = 0; i < 2; i++) { |
| 670 | | - const angle = (i === 0) ? Math.PI * 0.7 : -Math.PI * 0.7 |
| 671 | | - const wx = 0.25 + Math.sin(angle) * 0.33 |
| 672 | | - const wz = Math.cos(angle) * 0.33 |
| 759 | + const angle = (i === 0) ? Math.PI * 0.65 : -Math.PI * 0.65 |
| 760 | + const wx = 0.25 + Math.sin(angle) * 0.29 |
| 761 | + const wz = Math.cos(angle) * 0.29 |
| 673 | 762 | |
| 674 | | - const shaftWindowGeom = new THREE.PlaneGeometry(0.12, 0.15) |
| 763 | + const shaftWindowGeom = new THREE.CircleGeometry(0.06, 10) |
| 675 | 764 | const shaftWindow = new THREE.Mesh(shaftWindowGeom, windowMaterial) |
| 676 | | - shaftWindow.position.set(wx, 0.6, wz) |
| 765 | + shaftWindow.position.set(wx, 0.7, wz) |
| 677 | 766 | shaftWindow.rotation.y = angle + Math.PI |
| 678 | 767 | group.add(shaftWindow) |
| 679 | 768 | |
| 680 | 769 | // Frame |
| 681 | | - const frameGeom = new THREE.BoxGeometry(0.14, 0.17, 0.02) |
| 770 | + const frameGeom = new THREE.TorusGeometry(0.06, 0.01, 4, 12) |
| 682 | 771 | const frame = new THREE.Mesh(frameGeom, darkLeatherMaterial) |
| 683 | | - frame.position.set(wx, 0.6, wz) |
| 772 | + frame.position.set(wx, 0.7, wz) |
| 684 | 773 | frame.rotation.y = angle + Math.PI |
| 685 | 774 | group.add(frame) |
| 686 | 775 | } |
@@ -743,34 +832,36 @@ export function createBootHouse(gradientMap) { |
| 743 | 832 | } |
| 744 | 833 | chimneyGroup.add(smokeGroup) |
| 745 | 834 | |
| 746 | | - chimneyGroup.position.set(0.1, 1.0, -0.15) |
| 835 | + chimneyGroup.position.set(0.15, 1.1, -0.12) |
| 747 | 836 | group.add(chimneyGroup) |
| 748 | 837 | |
| 749 | 838 | // === DOOR === |
| 750 | 839 | |
| 751 | 840 | const doorGroup = new THREE.Group() |
| 752 | 841 | |
| 753 | | - // Arched door on the toe |
| 754 | | - const doorGeom = new THREE.BoxGeometry(0.15, 0.25, 0.03) |
| 842 | + // Arched door on the side of the toe/vamp area |
| 843 | + const doorGeom = new THREE.BoxGeometry(0.12, 0.22, 0.03) |
| 755 | 844 | const door = new THREE.Mesh(doorGeom, darkLeatherMaterial) |
| 756 | | - door.position.y = 0.125 |
| 845 | + door.position.y = 0.11 |
| 757 | 846 | doorGroup.add(door) |
| 758 | 847 | |
| 759 | 848 | // Door arch |
| 760 | | - const doorArchGeom = new THREE.SphereGeometry(0.075, 8, 4, 0, Math.PI * 2, 0, Math.PI / 2) |
| 849 | + const doorArchGeom = new THREE.SphereGeometry(0.06, 8, 4, 0, Math.PI * 2, 0, Math.PI / 2) |
| 761 | 850 | const doorArch = new THREE.Mesh(doorArchGeom, darkLeatherMaterial) |
| 762 | | - doorArch.position.y = 0.25 |
| 851 | + doorArch.position.y = 0.22 |
| 763 | 852 | doorArch.rotation.x = Math.PI |
| 764 | 853 | doorGroup.add(doorArch) |
| 765 | 854 | |
| 766 | 855 | // Door knob |
| 767 | | - const knobGeom = new THREE.SphereGeometry(0.018, 6, 4) |
| 856 | + const knobGeom = new THREE.SphereGeometry(0.015, 6, 4) |
| 768 | 857 | const knobMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap }) |
| 769 | 858 | const knob = new THREE.Mesh(knobGeom, knobMat) |
| 770 | | - knob.position.set(0.05, 0.12, 0.02) |
| 859 | + knob.position.set(0.04, 0.1, 0.02) |
| 771 | 860 | doorGroup.add(knob) |
| 772 | 861 | |
| 773 | | - doorGroup.position.set(-0.25, 0.12, 0.35) |
| 862 | + // Position door on the front-side of the boot |
| 863 | + doorGroup.position.set(-0.32, 0.1, 0.22) |
| 864 | + doorGroup.rotation.y = -0.4 |
| 774 | 865 | group.add(doorGroup) |
| 775 | 866 | |
| 776 | 867 | // === YARD WITH TALL GRASS PATCHES === |
@@ -780,12 +871,12 @@ export function createBootHouse(gradientMap) { |
| 780 | 871 | |
| 781 | 872 | // Create grass patches around the boot |
| 782 | 873 | const grassPositions = [ |
| 783 | | - { x: -0.7, z: 0.4, count: 8 }, |
| 784 | | - { x: -0.6, z: -0.35, count: 6 }, |
| 785 | | - { x: 0.7, z: 0.35, count: 7 }, |
| 786 | | - { x: 0.65, z: -0.3, count: 5 }, |
| 787 | | - { x: -0.1, z: -0.5, count: 6 }, |
| 788 | | - { x: 0.4, z: 0.5, count: 5 } |
| 874 | + { x: -0.8, z: 0.35, count: 8 }, |
| 875 | + { x: -0.75, z: -0.35, count: 6 }, |
| 876 | + { x: 0.7, z: 0.4, count: 7 }, |
| 877 | + { x: 0.75, z: -0.35, count: 5 }, |
| 878 | + { x: 0.0, z: -0.55, count: 6 }, |
| 879 | + { x: 0.0, z: 0.55, count: 5 } |
| 789 | 880 | ] |
| 790 | 881 | |
| 791 | 882 | for (const patch of grassPositions) { |
@@ -877,11 +968,11 @@ export function createBootHouse(gradientMap) { |
| 877 | 968 | sprite.add(pupilR) |
| 878 | 969 | |
| 879 | 970 | // Animation data |
| 880 | | - sprite.userData.orbitRadius = 0.5 + Math.random() * 0.3 |
| 971 | + sprite.userData.orbitRadius = 0.6 + Math.random() * 0.25 |
| 881 | 972 | sprite.userData.orbitSpeed = 0.8 + Math.random() * 0.6 |
| 882 | 973 | sprite.userData.orbitPhase = (i / 4) * Math.PI * 2 |
| 883 | 974 | sprite.userData.bobPhase = Math.random() * Math.PI * 2 |
| 884 | | - sprite.userData.orbitCenterX = 0.1 // Roughly center of boot |
| 975 | + sprite.userData.orbitCenterX = 0.0 // Center of boot footprint |
| 885 | 976 | sprite.userData.orbitCenterZ = 0 |
| 886 | 977 | |
| 887 | 978 | // Initial position |